Merge branch 'main' into shawn/winappsdkPwshUpdate

This commit is contained in:
Shawn Yuan (from Dev Box)
2025-12-09 14:44:35 +08:00
118 changed files with 2666 additions and 243 deletions

View File

@@ -1483,6 +1483,7 @@ rgh
rgn
rgs
rguid
rhk
RIDEV
RIGHTSCROLLBAR
riid
@@ -1588,6 +1589,7 @@ SHGDNF
SHGFI
SHIL
shinfo
shk
shlwapi
shobjidl
SHORTCUTATLEAST
@@ -1798,6 +1800,7 @@ tlbimp
tlc
tmain
TNP
toolgood
Toolhelp
toolwindow
TOPDOWNDIB

View File

@@ -60,6 +60,8 @@
"PowerToys.FancyZonesEditorCommon.dll",
"PowerToys.FancyZonesModuleInterface.dll",
"PowerToys.FancyZones.exe",
"FancyZonesCLI.exe",
"FancyZonesCLI.dll",
"PowerToys.GcodePreviewHandler.dll",
"PowerToys.GcodePreviewHandler.exe",
@@ -351,6 +353,11 @@
"Microsoft.SemanticKernel.Connectors.Ollama.dll",
"OllamaSharp.dll",
"boost_regex-vc143-mt-gd-x32-1_87.dll",
"boost_regex-vc143-mt-gd-x64-1_87.dll",
"boost_regex-vc143-mt-x32-1_87.dll",
"boost_regex-vc143-mt-x64-1_87.dll",
"UnitsNet.dll",
"UtfUnknown.dll",
"Wpf.Ui.dll"

View File

@@ -52,7 +52,12 @@ $nullVersionExceptions = @(
"System.Diagnostics.EventLog.Messages.dll",
"Microsoft.Windows.Widgets.dll",
"AdaptiveCards.ObjectModel.WinUI3.dll",
"AdaptiveCards.Rendering.WinUI3.dll") -join '|';
"AdaptiveCards.Rendering.WinUI3.dll",
"boost_regex_vc143_mt_gd_x32_1_87.dll",
"boost_regex_vc143_mt_gd_x64_1_87.dll",
"boost_regex_vc143_mt_x32_1_87.dll",
"boost_regex_vc143_mt_x64_1_87.dll"
) -join '|';
$totalFailure = 0;
Write-Host $DirPath;

View File

@@ -73,10 +73,10 @@
<PackageVersion Include="Microsoft.Windows.CsWinRT" Version="2.2.0" />
<PackageVersion Include="Microsoft.Windows.ImplementationLibrary" Version="1.0.231216.1"/>
<PackageVersion Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.6901" />
<PackageVersion Include="Microsoft.WindowsAppSDK" Version="1.8.250907003" />
<PackageVersion Include="Microsoft.WindowsAppSDK.Foundation" Version="1.8.250906002" />
<PackageVersion Include="Microsoft.WindowsAppSDK.AI" Version="1.8.37" />
<PackageVersion Include="Microsoft.WindowsAppSDK.Runtime" Version="1.8.250907003" />
<PackageVersion Include="Microsoft.WindowsAppSDK" Version="1.8.251106002" />
<PackageVersion Include="Microsoft.WindowsAppSDK.Foundation" Version="1.8.251104000" />
<PackageVersion Include="Microsoft.WindowsAppSDK.AI" Version="1.8.39" />
<PackageVersion Include="Microsoft.WindowsAppSDK.Runtime" Version="1.8.251106002" />
<PackageVersion Include="Microsoft.Xaml.Behaviors.WinUI.Managed" Version="2.0.9" />
<PackageVersion Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.39" />
<PackageVersion Include="ModernWpfUI" Version="0.9.4" />
@@ -115,6 +115,7 @@
<PackageVersion Include="System.IO.Abstractions.TestingHelpers" Version="22.0.13" />
<PackageVersion Include="System.Management" Version="9.0.10" />
<PackageVersion Include="System.Net.Http" Version="4.3.4" />
<PackageVersion Include="System.Numerics.Tensors" Version="9.0.11" />
<PackageVersion Include="System.Private.Uri" Version="4.3.2" />
<PackageVersion Include="System.Reactive" Version="6.0.1" />
<PackageVersion Include="System.Runtime.Caching" Version="9.0.10" />
@@ -122,6 +123,7 @@
<PackageVersion Include="System.Text.Encoding.CodePages" Version="9.0.10" />
<PackageVersion Include="System.Text.Json" Version="9.0.10" />
<PackageVersion Include="System.Text.RegularExpressions" Version="4.3.1" />
<PackageVersion Include="ToolGood.Words.Pinyin" Version="3.1.0.3" />
<PackageVersion Include="UnicodeInformation" Version="2.6.0" />
<PackageVersion Include="UnitsNet" Version="5.56.0" />
<PackageVersion Include="UTF.Unknown" Version="2.6.0" />

View File

@@ -75,6 +75,37 @@ OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <http://unlicense.org/>
```
### ToolGood.Words.Pinyin
We use the ToolGood.Words.Pinyin NuGet package for converting Chinese characters to pinyin.
**Source**: [https://github.com/toolgood/ToolGood.Words.Pinyin](https://github.com/toolgood/ToolGood.Words.Pinyin)
```
MIT License
Copyright (c) 2020 ToolGood
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
```
## Utility: Command Palette Built-in Extensions
### Calculator
@@ -1532,6 +1563,7 @@ SOFTWARE.
- SkiaSharp.Views.WinUI
- StreamJsonRpc
- StyleCop.Analyzers
- ToolGood.Words.Pinyin
- UnicodeInformation
- UnitsNet
- UTF.Unknown

View File

@@ -370,6 +370,10 @@
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/fancyzones/FancyZones/FancyZones.vcxproj" Id="ff1d7936-842a-4bbb-8bea-e9fe796de700" />
<Project Path="src/modules/fancyzones/FancyZonesCLI/FancyZonesCLI.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/fancyzones/FancyZonesEditorCommon/FancyZonesEditorCommon.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />

View File

@@ -38,7 +38,7 @@ For C# modules, the settings are accessed through the `SettingsUtils` class in t
using Microsoft.PowerToys.Settings.UI.Library;
// Read settings
var settings = SettingsUtils.GetSettings<ModuleSettings>("ModuleName");
var settings = SettingsUtils.Default.GetSettings<ModuleSettings>("ModuleName");
bool enabled = settings.Enabled;
```
@@ -49,7 +49,7 @@ using Microsoft.PowerToys.Settings.UI.Library;
// Write settings
settings.Enabled = true;
SettingsUtils.SaveSettings(settings.ToJsonString(), "ModuleName");
SettingsUtils.Default.SaveSettings(settings.ToJsonString(), "ModuleName");
```
## Settings Handling in Modules

View File

@@ -21,7 +21,7 @@ namespace Microsoft.PowerToys.UITest
public class SettingsConfigHelper
{
private static readonly JsonSerializerOptions IndentedJsonOptions = new() { WriteIndented = true };
private static readonly SettingsUtils SettingsUtils = new SettingsUtils();
private static readonly SettingsUtils SettingsUtils = SettingsUtils.Default;
/// <summary>
/// Configures global PowerToys settings to enable only specified modules and disable all others.

View File

@@ -16,9 +16,54 @@
namespace registry
{
namespace detail
{
struct on_exit
{
std::function<void()> f;
on_exit(std::function<void()> f) :
f{ std::move(f) } {}
~on_exit() { f(); }
};
template<class... Ts>
struct overloaded : Ts...
{
using Ts::operator()...;
};
template<class... Ts>
overloaded(Ts...) -> overloaded<Ts...>;
inline const wchar_t* getScopeName(HKEY scope)
{
if (scope == HKEY_LOCAL_MACHINE)
{
return L"HKLM";
}
else if (scope == HKEY_CURRENT_USER)
{
return L"HKCU";
}
else if (scope == HKEY_CLASSES_ROOT)
{
return L"HKCR";
}
else
{
return L"HK??";
}
}
}
namespace install_scope
{
const wchar_t INSTALL_SCOPE_REG_KEY[] = L"Software\\Classes\\powertoys\\";
const wchar_t UNINSTALL_REG_KEY[] = L"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall";
// Bundle UpgradeCode from PowerToys.wxs (with braces as stored in registry)
const wchar_t BUNDLE_UPGRADE_CODE[] = L"{6341382D-C0A9-4238-9188-BE9607E3FAB2}";
enum class InstallScope
{
@@ -26,8 +71,67 @@ namespace registry
PerUser,
};
// Helper function to find PowerToys bundle in Windows Uninstall registry by BundleUpgradeCode
inline bool find_powertoys_bundle_in_uninstall_registry(HKEY rootKey)
{
HKEY uninstallKey{};
if (RegOpenKeyExW(rootKey, UNINSTALL_REG_KEY, 0, KEY_READ, &uninstallKey) != ERROR_SUCCESS)
{
return false;
}
detail::on_exit closeUninstallKey{ [uninstallKey] { RegCloseKey(uninstallKey); } };
DWORD index = 0;
wchar_t subKeyName[256];
// Enumerate all subkeys under Uninstall
while (RegEnumKeyW(uninstallKey, index++, subKeyName, 256) == ERROR_SUCCESS)
{
HKEY productKey{};
if (RegOpenKeyExW(uninstallKey, subKeyName, 0, KEY_READ, &productKey) != ERROR_SUCCESS)
{
continue;
}
detail::on_exit closeProductKey{ [productKey] { RegCloseKey(productKey); } };
// Check BundleUpgradeCode value (specific to WiX Bundle installations)
wchar_t bundleUpgradeCode[256]{};
DWORD bundleUpgradeCodeSize = sizeof(bundleUpgradeCode);
if (RegQueryValueExW(productKey, L"BundleUpgradeCode", nullptr, nullptr,
reinterpret_cast<LPBYTE>(bundleUpgradeCode), &bundleUpgradeCodeSize) == ERROR_SUCCESS)
{
if (_wcsicmp(bundleUpgradeCode, BUNDLE_UPGRADE_CODE) == 0)
{
return true;
}
}
}
return false;
}
inline const InstallScope get_current_install_scope()
{
// 1. Check HKCU Uninstall registry first (user-level bundle)
// Note: MSI components are always in HKLM regardless of install scope,
// but the Bundle entry will be in HKCU for per-user installations
if (find_powertoys_bundle_in_uninstall_registry(HKEY_CURRENT_USER))
{
Logger::info(L"Found user-level PowerToys bundle via BundleUpgradeCode in HKCU");
return InstallScope::PerUser;
}
// 2. Check HKLM Uninstall registry (machine-level bundle)
if (find_powertoys_bundle_in_uninstall_registry(HKEY_LOCAL_MACHINE))
{
Logger::info(L"Found machine-level PowerToys bundle via BundleUpgradeCode in HKLM");
return InstallScope::PerMachine;
}
// 3. Fallback to legacy custom registry key detection
Logger::info(L"PowerToys bundle not found in Uninstall registry, falling back to legacy detection");
// Open HKLM key
HKEY perMachineKey{};
if (RegOpenKeyExW(HKEY_LOCAL_MACHINE,
@@ -45,6 +149,7 @@ namespace registry
&perUserKey) != ERROR_SUCCESS)
{
// both keys are missing
Logger::warn(L"No PowerToys installation detected, defaulting to PerMachine");
return InstallScope::PerMachine;
}
else
@@ -96,47 +201,6 @@ namespace registry
template<class>
inline constexpr bool always_false_v = false;
namespace detail
{
struct on_exit
{
std::function<void()> f;
on_exit(std::function<void()> f) :
f{ std::move(f) } {}
~on_exit() { f(); }
};
template<class... Ts>
struct overloaded : Ts...
{
using Ts::operator()...;
};
template<class... Ts>
overloaded(Ts...) -> overloaded<Ts...>;
inline const wchar_t* getScopeName(HKEY scope)
{
if (scope == HKEY_LOCAL_MACHINE)
{
return L"HKLM";
}
else if (scope == HKEY_CURRENT_USER)
{
return L"HKCU";
}
else if (scope == HKEY_CLASSES_ROOT)
{
return L"HKCR";
}
else
{
return L"HK??";
}
}
}
struct ValueChange
{
using value_t = std::variant<DWORD, std::wstring>;

View File

@@ -18,7 +18,7 @@ namespace PowerToys.DSC.UnitTests.SettingsResourceTests;
public abstract class SettingsResourceModuleTest<TSettingsConfig> : BaseDscTest
where TSettingsConfig : ISettingsConfig, new()
{
private readonly SettingsUtils _settingsUtils = new();
private readonly SettingsUtils _settingsUtils = SettingsUtils.Default;
private TSettingsConfig _originalSettings;
protected TSettingsConfig DefaultSettings => new();

View File

@@ -18,7 +18,7 @@ namespace PowerToys.DSC.Models.FunctionData;
public sealed class SettingsFunctionData<TSettingsConfig> : BaseFunctionData, ISettingsFunctionData
where TSettingsConfig : ISettingsConfig, new()
{
private static readonly SettingsUtils _settingsUtils = new();
private static readonly SettingsUtils _settingsUtils = SettingsUtils.Default;
private static readonly TSettingsConfig _settingsConfig = new();
private readonly SettingsResourceObject<TSettingsConfig> _input;

View File

@@ -62,7 +62,7 @@ namespace Hosts.Settings
public UserSettings()
{
_settingsUtils = new SettingsUtils();
_settingsUtils = SettingsUtils.Default;
var defaultSettings = new HostsProperties();
ShowStartupWarning = defaultSettings.ShowStartupWarning;
LoopbackDuplicates = defaultSettings.LoopbackDuplicates;

View File

@@ -11,7 +11,7 @@ namespace MeasureToolUI
{
public sealed class Settings
{
private static readonly SettingsUtils ModuleSettings = new();
private static readonly SettingsUtils ModuleSettings = SettingsUtils.Default;
public MeasureToolMeasureStyle DefaultMeasureStyle
{

View File

@@ -53,7 +53,7 @@ internal sealed class SettingsHelper
lock (this.LockObject)
{
{
var settingsUtils = new SettingsUtils();
var settingsUtils = SettingsUtils.Default;
// set this to 1 to disable retries
var remainingRetries = 5;

View File

@@ -192,7 +192,7 @@ namespace MouseWithoutBorders.Class
internal Settings()
{
_settingsUtils = new SettingsUtils();
_settingsUtils = SettingsUtils.Default;
_watcher = SettingsHelper.GetFileWatcher("MouseWithoutBorders", "settings.json", () =>
{

View File

@@ -29,7 +29,7 @@ namespace PowerOCR.Settings
[ImportingConstructor]
public UserSettings(Helpers.IThrottledActionInvoker throttledActionInvoker)
{
_settingsUtils = new SettingsUtils();
_settingsUtils = SettingsUtils.Default;
ActivationShortcut = new SettingItem<string>(DefaultActivationShortcut);
PreferredLanguage = new SettingItem<string>(string.Empty);

View File

@@ -9,7 +9,7 @@ namespace WorkspacesEditor.Utils
public class Settings
{
private const string WorkspacesModuleName = "Workspaces";
private static readonly SettingsUtils _settingsUtils = new();
private static readonly SettingsUtils _settingsUtils = SettingsUtils.Default;
public static WorkspacesSettings ReadSettings()
{

View File

@@ -133,7 +133,7 @@ namespace WorkspacesEditor.ViewModels
_orderByIndex = value;
OnPropertyChanged(new PropertyChangedEventArgs(nameof(WorkspacesView)));
settings.Properties.SortBy = (WorkspacesProperties.SortByProperty)value;
settings.Save(new SettingsUtils());
settings.Save(SettingsUtils.Default);
}
}

View File

@@ -60,7 +60,7 @@ namespace Awake.Core
{
_tokenSource = new CancellationTokenSource();
_stateQueue = [];
ModuleSettings = new SettingsUtils();
ModuleSettings = SettingsUtils.Default;
}
internal static void StartMonitor()

View File

@@ -51,7 +51,7 @@ namespace Awake
private static async Task<int> Main(string[] args)
{
_settingsUtils = new SettingsUtils();
_settingsUtils = SettingsUtils.Default;
LockMutex = new Mutex(true, Core.Constants.AppName, out bool instantiated);

View File

@@ -0,0 +1,10 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace Microsoft.CmdPal.Core.ViewModels.Messages;
/// <summary>
/// Used to navigate left in a grid view when pressing the Left arrow key in the SearchBox.
/// </summary>
public record NavigateLeftCommand;

View File

@@ -0,0 +1,10 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace Microsoft.CmdPal.Core.ViewModels.Messages;
/// <summary>
/// Used to navigate right in a grid view when pressing the Right arrow key in the SearchBox.
/// </summary>
public record NavigateRightCommand;

View File

@@ -12,6 +12,7 @@ using Microsoft.CmdPal.Core.ViewModels.Messages;
using Microsoft.CmdPal.Ext.Apps;
using Microsoft.CmdPal.Ext.Apps.Programs;
using Microsoft.CmdPal.Ext.Apps.State;
using Microsoft.CmdPal.UI.ViewModels.Commands;
using Microsoft.CmdPal.UI.ViewModels.Messages;
using Microsoft.CmdPal.UI.ViewModels.Properties;
using Microsoft.CommandPalette.Extensions;
@@ -44,6 +45,9 @@ public partial class MainListPage : DynamicListPage,
private List<Scored<IListItem>>? _filteredItems;
private List<Scored<IListItem>>? _filteredApps;
private List<Scored<IListItem>>? _fallbackItems;
// Keep as IEnumerable for deferred execution. Fallback item titles are updated
// asynchronously, so scoring must happen lazily when GetItems is called.
private IEnumerable<Scored<IListItem>>? _scoredFallbackItems;
private bool _includeApps;
private bool _filteredItemsIncludesApps;
@@ -155,42 +159,18 @@ public partial class MainListPage : DynamicListPage,
public override IListItem[] GetItems()
{
if (string.IsNullOrEmpty(SearchText))
lock (_tlcManager.TopLevelCommands)
{
lock (_tlcManager.TopLevelCommands)
{
return _tlcManager
.TopLevelCommands
.Where(tlc => !tlc.IsFallback && !string.IsNullOrEmpty(tlc.Title))
.ToArray();
}
}
else
{
lock (_tlcManager.TopLevelCommands)
{
var limitedApps = new List<Scored<IListItem>>();
// Fuzzy matching can produce a lot of results, so we want to limit the
// number of apps we show at once if it's a large set.
if (_filteredApps?.Count > 0)
{
limitedApps = _filteredApps.OrderByDescending(s => s.Score).Take(_appResultLimit).ToList();
}
var items = Enumerable.Empty<Scored<IListItem>>()
.Concat(_filteredItems is not null ? _filteredItems : [])
.Concat(_scoredFallbackItems is not null ? _scoredFallbackItems : [])
.Concat(limitedApps)
.OrderByDescending(o => o.Score)
// Add fallback items post-sort so they are always at the end of the list
// and eventually ordered based on user preference
.Concat(_fallbackItems is not null ? _fallbackItems.Where(w => !string.IsNullOrEmpty(w.Item.Title)) : [])
.Select(s => s.Item)
.ToArray();
return items;
}
// Either return the top-level commands (no search text), or the merged and
// filtered results.
return string.IsNullOrEmpty(SearchText)
? _tlcManager.TopLevelCommands.Where(tlc => !tlc.IsFallback && !string.IsNullOrEmpty(tlc.Title)).ToArray()
: MainListPageResultFactory.Create(
_filteredItems,
_scoredFallbackItems?.ToList(),
_filteredApps,
_fallbackItems,
_appResultLimit);
}
}

View File

@@ -0,0 +1,156 @@
// 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.
#pragma warning disable IDE0007 // Use implicit type
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
namespace Microsoft.CmdPal.UI.ViewModels.Commands;
internal static class MainListPageResultFactory
{
/// <summary>
/// Creates a merged and ordered array of results from multiple scored input lists,
/// applying an application result limit and filtering fallback items as needed.
/// </summary>
public static IListItem[] Create(
IList<Scored<IListItem>>? filteredItems,
IList<Scored<IListItem>>? scoredFallbackItems,
IList<Scored<IListItem>>? filteredApps,
IList<Scored<IListItem>>? fallbackItems,
int appResultLimit)
{
if (appResultLimit < 0)
{
throw new ArgumentOutOfRangeException(
nameof(appResultLimit), "App result limit must be non-negative.");
}
int len1 = filteredItems?.Count ?? 0;
int len2 = scoredFallbackItems?.Count ?? 0;
// Apps are pre-sorted, so we just need to take the top N, limited by appResultLimit.
int len3 = Math.Min(filteredApps?.Count ?? 0, appResultLimit);
// Allocate the exact size of the result array.
int totalCount = len1 + len2 + len3 + GetNonEmptyFallbackItemsCount(fallbackItems);
var result = new IListItem[totalCount];
// Three-way stable merge of already-sorted lists.
int idx1 = 0, idx2 = 0, idx3 = 0;
int writePos = 0;
// Merge while all three lists have items. To maintain a stable sort, the
// priority is: list1 > list2 > list3 when scores are equal.
while (idx1 < len1 && idx2 < len2 && idx3 < len3)
{
// Using null-forgiving operator as we have already checked against lengths.
int score1 = filteredItems![idx1].Score;
int score2 = scoredFallbackItems![idx2].Score;
int score3 = filteredApps![idx3].Score;
if (score1 >= score2 && score1 >= score3)
{
result[writePos++] = filteredItems[idx1++].Item;
}
else if (score2 >= score3)
{
result[writePos++] = scoredFallbackItems[idx2++].Item;
}
else
{
result[writePos++] = filteredApps[idx3++].Item;
}
}
// Two-way merges for remaining pairs.
while (idx1 < len1 && idx2 < len2)
{
if (filteredItems![idx1].Score >= scoredFallbackItems![idx2].Score)
{
result[writePos++] = filteredItems[idx1++].Item;
}
else
{
result[writePos++] = scoredFallbackItems[idx2++].Item;
}
}
while (idx1 < len1 && idx3 < len3)
{
if (filteredItems![idx1].Score >= filteredApps![idx3].Score)
{
result[writePos++] = filteredItems[idx1++].Item;
}
else
{
result[writePos++] = filteredApps[idx3++].Item;
}
}
while (idx2 < len2 && idx3 < len3)
{
if (scoredFallbackItems![idx2].Score >= filteredApps![idx3].Score)
{
result[writePos++] = scoredFallbackItems[idx2++].Item;
}
else
{
result[writePos++] = filteredApps[idx3++].Item;
}
}
// Drain remaining items from a non-empty list.
while (idx1 < len1)
{
result[writePos++] = filteredItems![idx1++].Item;
}
while (idx2 < len2)
{
result[writePos++] = scoredFallbackItems![idx2++].Item;
}
while (idx3 < len3)
{
result[writePos++] = filteredApps![idx3++].Item;
}
// Append filtered fallback items. Fallback items are added post-sort so they are
// always at the end of the list and eventually ordered based on user preference.
if (fallbackItems is not null)
{
for (int i = 0; i < fallbackItems.Count; i++)
{
var item = fallbackItems[i].Item;
if (!string.IsNullOrEmpty(item.Title))
{
result[writePos++] = item;
}
}
}
return result;
}
private static int GetNonEmptyFallbackItemsCount(IList<Scored<IListItem>>? fallbackItems)
{
int fallbackItemsCount = 0;
if (fallbackItems is not null)
{
for (int i = 0; i < fallbackItems.Count; i++)
{
if (!string.IsNullOrEmpty(fallbackItems[i].Item.Title))
{
fallbackItemsCount++;
}
}
}
return fallbackItemsCount;
}
}
#pragma warning restore IDE0007 // Use implicit type

View File

@@ -208,21 +208,32 @@ public sealed partial class SearchBar : UserControl,
e.Handled = true;
}
else if (e.Key == VirtualKey.Left)
{
// Check if we're in a grid view, and if so, send grid navigation command
var isGridView = CurrentPageViewModel is ListViewModel { IsGridView: true };
// Special handling is required if we're in grid view.
if (isGridView)
{
WeakReferenceMessenger.Default.Send<NavigateLeftCommand>();
e.Handled = true;
}
}
else if (e.Key == VirtualKey.Right)
{
// Check if the "replace search text with suggestion" feature from 0.4-0.5 is enabled.
// If it isn't, then only use the suggestion when the caret is at the end of the input.
if (!IsTextToSuggestEnabled)
{
if (_textToSuggest != null &&
if (!string.IsNullOrEmpty(_textToSuggest) &&
FilterBox.SelectionStart == FilterBox.Text.Length)
{
FilterBox.Text = _textToSuggest;
FilterBox.Select(_textToSuggest.Length, 0);
e.Handled = true;
return;
}
return;
}
// Here, we're using the "replace search text with suggestion" feature.
@@ -232,6 +243,20 @@ public sealed partial class SearchBar : UserControl,
_lastText = null;
DoFilterBoxUpdate();
}
// Wouldn't want to perform text completion *and* move the selected item, so only perform this if text suggestion wasn't performed.
if (!e.Handled)
{
// Check if we're in a grid view, and if so, send grid navigation command
var isGridView = CurrentPageViewModel is ListViewModel { IsGridView: true };
// Special handling is required if we're in grid view.
if (isGridView)
{
WeakReferenceMessenger.Default.Send<NavigateRightCommand>();
e.Handled = true;
}
}
}
else if (e.Key == VirtualKey.Down)
{
@@ -274,6 +299,8 @@ public sealed partial class SearchBar : UserControl,
e.Key == VirtualKey.Up ||
e.Key == VirtualKey.Down ||
e.Key == VirtualKey.Left ||
e.Key == VirtualKey.Right ||
e.Key == VirtualKey.RightMenu ||
e.Key == VirtualKey.LeftMenu ||

View File

@@ -26,6 +26,8 @@ namespace Microsoft.CmdPal.UI;
public sealed partial class ListPage : Page,
IRecipient<NavigateNextCommand>,
IRecipient<NavigatePreviousCommand>,
IRecipient<NavigateLeftCommand>,
IRecipient<NavigateRightCommand>,
IRecipient<NavigatePageDownCommand>,
IRecipient<NavigatePageUpCommand>,
IRecipient<ActivateSelectedListItemMessage>,
@@ -85,6 +87,8 @@ public sealed partial class ListPage : Page,
// RegisterAll isn't AOT compatible
WeakReferenceMessenger.Default.Register<NavigateNextCommand>(this);
WeakReferenceMessenger.Default.Register<NavigatePreviousCommand>(this);
WeakReferenceMessenger.Default.Register<NavigateLeftCommand>(this);
WeakReferenceMessenger.Default.Register<NavigateRightCommand>(this);
WeakReferenceMessenger.Default.Register<NavigatePageDownCommand>(this);
WeakReferenceMessenger.Default.Register<NavigatePageUpCommand>(this);
WeakReferenceMessenger.Default.Register<ActivateSelectedListItemMessage>(this);
@@ -99,6 +103,8 @@ public sealed partial class ListPage : Page,
WeakReferenceMessenger.Default.Unregister<NavigateNextCommand>(this);
WeakReferenceMessenger.Default.Unregister<NavigatePreviousCommand>(this);
WeakReferenceMessenger.Default.Unregister<NavigateLeftCommand>(this);
WeakReferenceMessenger.Default.Unregister<NavigateRightCommand>(this);
WeakReferenceMessenger.Default.Unregister<NavigatePageDownCommand>(this);
WeakReferenceMessenger.Default.Unregister<NavigatePageUpCommand>(this);
WeakReferenceMessenger.Default.Unregister<ActivateSelectedListItemMessage>(this);
@@ -257,25 +263,71 @@ public sealed partial class ListPage : Page,
// And then have these commands manipulate that state being bound to the UI instead
// We may want to see how other non-list UIs need to behave to make this decision
// At least it's decoupled from the SearchBox now :)
if (ItemView.SelectedIndex < ItemView.Items.Count - 1)
if (ViewModel?.IsGridView == true)
{
ItemView.SelectedIndex++;
// For grid views, use spatial navigation (down)
HandleGridArrowNavigation(VirtualKey.Down);
}
else
{
ItemView.SelectedIndex = 0;
// For list views, use simple linear navigation
if (ItemView.SelectedIndex < ItemView.Items.Count - 1)
{
ItemView.SelectedIndex++;
}
else
{
ItemView.SelectedIndex = 0;
}
}
}
public void Receive(NavigatePreviousCommand message)
{
if (ItemView.SelectedIndex > 0)
if (ViewModel?.IsGridView == true)
{
ItemView.SelectedIndex--;
// For grid views, use spatial navigation (up)
HandleGridArrowNavigation(VirtualKey.Up);
}
else
{
ItemView.SelectedIndex = ItemView.Items.Count - 1;
// For list views, use simple linear navigation
if (ItemView.SelectedIndex > 0)
{
ItemView.SelectedIndex--;
}
else
{
ItemView.SelectedIndex = ItemView.Items.Count - 1;
}
}
}
public void Receive(NavigateLeftCommand message)
{
// For grid views, use spatial navigation. For list views, just move up.
if (ViewModel?.IsGridView == true)
{
HandleGridArrowNavigation(VirtualKey.Left);
}
else
{
// In list view, left arrow doesn't navigate
// This maintains consistency with the SearchBar behavior
}
}
public void Receive(NavigateRightCommand message)
{
// For grid views, use spatial navigation. For list views, just move down.
if (ViewModel?.IsGridView == true)
{
HandleGridArrowNavigation(VirtualKey.Right);
}
else
{
// In list view, right arrow doesn't navigate
// This maintains consistency with the SearchBar behavior
}
}
@@ -514,6 +566,130 @@ public sealed partial class ListPage : Page,
return null;
}
// Find a logical neighbor in the requested direction using containers' positions.
private void HandleGridArrowNavigation(VirtualKey key)
{
if (ItemView.Items.Count == 0)
{
// No items, goodbye.
return;
}
var currentIndex = ItemView.SelectedIndex;
if (currentIndex < 0)
{
// -1 is a valid value (no item currently selected)
currentIndex = 0;
ItemView.SelectedIndex = 0;
}
try
{
// Try to compute using container positions; if not available, fall back to simple +/-1.
var currentContainer = ItemView.ContainerFromIndex(currentIndex) as FrameworkElement;
if (currentContainer is not null && currentContainer.ActualWidth != 0 && currentContainer.ActualHeight != 0)
{
// Use center of current container as reference
var curPoint = currentContainer.TransformToVisual(ItemView).TransformPoint(new Point(0, 0));
var curCenterX = curPoint.X + (currentContainer.ActualWidth / 2.0);
var curCenterY = curPoint.Y + (currentContainer.ActualHeight / 2.0);
var bestScore = double.MaxValue;
var bestIndex = currentIndex;
for (var i = 0; i < ItemView.Items.Count; i++)
{
if (i == currentIndex)
{
continue;
}
if (ItemView.ContainerFromIndex(i) is FrameworkElement c && c.ActualWidth > 0 && c.ActualHeight > 0)
{
var p = c.TransformToVisual(ItemView).TransformPoint(new Point(0, 0));
var centerX = p.X + (c.ActualWidth / 2.0);
var centerY = p.Y + (c.ActualHeight / 2.0);
var dx = centerX - curCenterX;
var dy = centerY - curCenterY;
var candidate = false;
var score = double.MaxValue;
switch (key)
{
case VirtualKey.Left:
if (dx < 0)
{
candidate = true;
score = Math.Abs(dy) + (Math.Abs(dx) * 0.7);
}
break;
case VirtualKey.Right:
if (dx > 0)
{
candidate = true;
score = Math.Abs(dy) + (Math.Abs(dx) * 0.7);
}
break;
case VirtualKey.Up:
if (dy < 0)
{
candidate = true;
score = Math.Abs(dx) + (Math.Abs(dy) * 0.7);
}
break;
case VirtualKey.Down:
if (dy > 0)
{
candidate = true;
score = Math.Abs(dx) + (Math.Abs(dy) * 0.7);
}
break;
}
if (candidate && score < bestScore)
{
bestScore = score;
bestIndex = i;
}
}
}
if (bestIndex != currentIndex)
{
ItemView.SelectedIndex = bestIndex;
ItemView.ScrollIntoView(ItemView.SelectedItem);
}
return;
}
}
catch
{
// ignore transform errors and fall back
}
// fallback linear behavior
var fallback = key switch
{
VirtualKey.Left => Math.Max(0, currentIndex - 1),
VirtualKey.Right => Math.Min(ItemView.Items.Count - 1, currentIndex + 1),
VirtualKey.Up => Math.Max(0, currentIndex - 1),
VirtualKey.Down => Math.Min(ItemView.Items.Count - 1, currentIndex + 1),
_ => currentIndex,
};
if (fallback != currentIndex)
{
ItemView.SelectedIndex = fallback;
ItemView.ScrollIntoView(ItemView.SelectedItem);
}
}
private void Items_OnContextRequested(UIElement sender, ContextRequestedEventArgs e)
{
var (item, element) = e.OriginalSource switch
@@ -564,9 +740,27 @@ public sealed partial class ListPage : Page,
private void Items_PreviewKeyDown(object sender, KeyRoutedEventArgs e)
{
// Track keyboard as the last input source for activation logic.
if (e.Key is VirtualKey.Enter or VirtualKey.Space)
{
_lastInputSource = InputSource.Keyboard;
return;
}
// Handle arrow navigation when we're showing a grid.
if (ViewModel?.IsGridView == true)
{
switch (e.Key)
{
case VirtualKey.Left:
case VirtualKey.Right:
case VirtualKey.Up:
case VirtualKey.Down:
_lastInputSource = InputSource.Keyboard;
HandleGridArrowNavigation(e.Key);
e.Handled = true;
break;
}
}
}

View File

@@ -0,0 +1,161 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.CmdPal.UI.ViewModels.Commands;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Windows.Foundation;
namespace Microsoft.CmdPal.UI.ViewModels.UnitTests;
[TestClass]
public partial class MainListPageResultFactoryTests
{
private sealed partial class MockListItem : IListItem
{
public string Title { get; set; } = string.Empty;
public string Subtitle { get; set; } = string.Empty;
public ICommand Command => new NoOpCommand();
public IDetails? Details => null;
public IIconInfo? Icon => null;
public string Section => throw new NotImplementedException();
public ITag[] Tags => throw new NotImplementedException();
public string TextToSuggest => throw new NotImplementedException();
public IContextItem[] MoreCommands => throw new NotImplementedException();
#pragma warning disable CS0067 // The event is never used
public event TypedEventHandler<object, IPropChangedEventArgs>? PropChanged;
#pragma warning restore CS0067 // The event is never used
public override string ToString() => Title;
}
private static Scored<IListItem> S(string title, int score)
{
return new Scored<IListItem>
{
Score = score,
Item = new MockListItem { Title = title },
};
}
[TestMethod]
public void Merge_PrioritizesListsCorrectly()
{
var filtered = new List<Scored<IListItem>>
{
S("F1", 100),
S("F2", 50),
};
var scoredFallback = new List<Scored<IListItem>>
{
S("SF1", 100),
S("SF2", 60),
};
var apps = new List<Scored<IListItem>>
{
S("A1", 100),
S("A2", 55),
};
// Fallbacks are not scored.
var fallbacks = new List<Scored<IListItem>>
{
S("FB1", 0),
S("FB2", 0),
};
var result = MainListPageResultFactory.Create(
filtered,
scoredFallback,
apps,
fallbacks,
appResultLimit: 10);
// Expected order:
// 100: F1, SF1, A1
// 60: SF2
// 55: A2
// 50: F2
// Then fallbacks in original order: FB1, FB2
var titles = result.Select(r => r.Title).ToArray();
#pragma warning disable CA1861 // Avoid constant arrays as arguments
CollectionAssert.AreEqual(
new[] { "F1", "SF1", "A1", "SF2", "A2", "F2", "FB1", "FB2" },
titles);
#pragma warning restore CA1861 // Avoid constant arrays as arguments
}
[TestMethod]
public void Merge_AppliesAppLimit()
{
var apps = new List<Scored<IListItem>>
{
S("A1", 100),
S("A2", 90),
S("A3", 80),
};
var result = MainListPageResultFactory.Create(
null,
null,
apps,
null,
2);
Assert.AreEqual(2, result.Length);
Assert.AreEqual("A1", result[0].Title);
Assert.AreEqual("A2", result[1].Title);
}
[TestMethod]
public void Merge_FiltersEmptyFallbacks()
{
var fallbacks = new List<Scored<IListItem>>
{
S("FB1", 0),
S(string.Empty, 0),
S("FB3", 0),
};
var result = MainListPageResultFactory.Create(
null,
null,
null,
fallbacks,
appResultLimit: 10);
Assert.AreEqual(2, result.Length);
Assert.AreEqual("FB1", result[0].Title);
Assert.AreEqual("FB3", result[1].Title);
}
[TestMethod]
public void Merge_HandlesNullLists()
{
var result = MainListPageResultFactory.Create(
null,
null,
null,
null,
appResultLimit: 10);
Assert.IsNotNull(result);
Assert.AreEqual(0, result.Length);
}
}

View File

@@ -2,6 +2,10 @@
// 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.Globalization;
using ToolGood.Words.Pinyin;
namespace Microsoft.CommandPalette.Extensions.Toolkit;
// Inspired by the fuzzy.rs from edit.exe
@@ -9,6 +13,21 @@ public static class FuzzyStringMatcher
{
private const int NOMATCH = 0;
/// <summary>
/// Gets a value indicating whether to support Chinese PinYin.
/// Automatically enabled when the system UI culture is Simplified Chinese.
/// </summary>
public static bool ChinesePinYinSupport { get; } = IsSimplifiedChinese();
private static bool IsSimplifiedChinese()
{
var culture = CultureInfo.CurrentUICulture;
// Detect Simplified Chinese: zh-CN, zh-Hans, zh-Hans-*
return culture.Name.StartsWith("zh-CN", StringComparison.OrdinalIgnoreCase)
|| culture.Name.StartsWith("zh-Hans", StringComparison.OrdinalIgnoreCase);
}
public static int ScoreFuzzy(string needle, string haystack, bool allowNonContiguousMatches = true)
{
var (s, _) = ScoreFuzzyWithPositions(needle, haystack, allowNonContiguousMatches);
@@ -16,6 +35,28 @@ public static class FuzzyStringMatcher
}
public static (int Score, List<int> Positions) ScoreFuzzyWithPositions(string needle, string haystack, bool allowNonContiguousMatches)
=> ScoreAllFuzzyWithPositions(needle, haystack, allowNonContiguousMatches).MaxBy(i => i.Score);
public static IEnumerable<(int Score, List<int> Positions)> ScoreAllFuzzyWithPositions(string needle, string haystack, bool allowNonContiguousMatches)
{
List<string> needles = [needle];
List<string> haystacks = [haystack];
if (ChinesePinYinSupport)
{
// Remove IME composition split characters.
var input = needle.Replace("'", string.Empty);
needles.Add(WordsHelper.GetPinyin(input));
if (WordsHelper.HasChinese(haystack))
{
haystacks.Add(WordsHelper.GetPinyin(haystack));
}
}
return needles.SelectMany(i => haystacks.Select(j => ScoreFuzzyWithPositionsInternal(i, j, allowNonContiguousMatches)));
}
private static (int Score, List<int> Positions) ScoreFuzzyWithPositionsInternal(string needle, string haystack, bool allowNonContiguousMatches)
{
if (string.IsNullOrEmpty(haystack) || string.IsNullOrEmpty(needle))
{

View File

@@ -42,6 +42,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="System.Drawing.Common" />
<PackageReference Include="ToolGood.Words.Pinyin" />
<!-- This line forces the WebView2 version used by Windows App SDK to be the one we expect from Directory.Packages.props . -->
</ItemGroup>

View File

@@ -4,7 +4,7 @@
<PathToRoot>..\..\..\..\..\</PathToRoot>
<WasdkNuget>$(PathToRoot)packages\Microsoft.WindowsAppSDK.1.8.250907003</WasdkNuget>
<CppWinRTNuget>$(PathToRoot)packages\Microsoft.Windows.CppWinRT.2.0.240111.5</CppWinRTNuget>
<WindowsSdkBuildToolsNuget>$(PathToRoot)packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.4188</WindowsSdkBuildToolsNuget>
<WindowsSdkBuildToolsNuget>$(PathToRoot)packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.6901</WindowsSdkBuildToolsNuget>
<WebView2Nuget>$(PathToRoot)packages\Microsoft.Web.WebView2.1.0.2903.40</WebView2Nuget>
</PropertyGroup>
<Import Project="$(WasdkNuget)\build\native\Microsoft.WindowsAppSDK.props" Condition="Exists('$(WasdkNuget)\build\native\Microsoft.WindowsAppSDK.props')" />

View File

@@ -12,6 +12,6 @@
<package id="Microsoft.WindowsAppSDK.InteractiveExperiences" version="1.8.250906004" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK.Widgets" version="1.8.250904007" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK.AI" version="1.8.37" targetFramework="native" />
<package id="Microsoft.Windows.SDK.BuildTools" version="10.0.26100.4188" targetFramework="native" />
<package id="Microsoft.Windows.SDK.BuildTools" version="10.0.26100.6901" targetFramework="native" />
<package id="Microsoft.Windows.SDK.BuildTools.MSIX" version="1.7.20250829.1" targetFramework="native" />
</packages>
</packages>

View File

@@ -54,9 +54,15 @@ if ($IsAzurePipelineBuild) {
} else {
$nugetPath = (Join-Path $PSScriptRoot "NugetWrapper.cmd")
}
$solutionPath = (Join-Path $PSScriptRoot "..\..\..\..\..\PowerToys.slnx")
if (($BuildStep -ieq "all") -Or ($BuildStep -ieq "build")) {
& $nugetPath restore (Join-Path $PSScriptRoot "..\..\..\..\..\PowerToys.slnx")
$restoreArgs = @(
$solutionPath
"/t:Restore"
"/p:RestorePackagesConfig=true"
)
& $msbuildPath $restoreArgs
Try {
foreach ($config in $Configuration.Split(",")) {

View File

@@ -45,7 +45,7 @@ namespace ColorPicker.Settings
[ImportingConstructor]
public UserSettings(Helpers.IThrottledActionInvoker throttledActionInvoker)
{
_settingsUtils = new SettingsUtils();
_settingsUtils = SettingsUtils.Default;
ChangeCursor = new SettingItem<bool>(true);
ActivationShortcut = new SettingItem<string>(DefaultActivationShortcut);
CopiedColorRepresentation = new SettingItem<string>(ColorRepresentationType.HEX.ToString());

View File

@@ -0,0 +1,90 @@
// 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;
using System.IO;
using System.Linq;
namespace FancyZonesCLI.Commands;
/// <summary>
/// Editor and Settings commands.
/// </summary>
internal static class EditorCommands
{
public static (int ExitCode, string Output) OpenEditor()
{
var editorExe = "PowerToys.FancyZonesEditor.exe";
// Check if editor-parameters.json exists
if (!FancyZonesData.EditorParametersExist())
{
return (1, "Error: editor-parameters.json not found.\nPlease launch FancyZones Editor using Win+` (Win+Backtick) hotkey first.");
}
// Check if editor is already running
var existingProcess = Process.GetProcessesByName("PowerToys.FancyZonesEditor").FirstOrDefault();
if (existingProcess != null)
{
NativeMethods.SetForegroundWindow(existingProcess.MainWindowHandle);
return (0, "FancyZones Editor is already running. Brought window to foreground.");
}
// Only check same directory as CLI
var editorPath = Path.Combine(AppContext.BaseDirectory, editorExe);
if (File.Exists(editorPath))
{
try
{
Process.Start(new ProcessStartInfo
{
FileName = editorPath,
UseShellExecute = true,
});
return (0, "FancyZones Editor launched successfully.");
}
catch (Exception ex)
{
return (1, $"Failed to launch: {ex.Message}");
}
}
return (1, $"Error: Could not find {editorExe} in {AppContext.BaseDirectory}");
}
public static (int ExitCode, string Output) OpenSettings()
{
try
{
// Find PowerToys.exe in common locations
string powertoysExe = null;
// Check in the same directory as the CLI (typical for dev builds)
var sameDirPath = Path.Combine(AppContext.BaseDirectory, "PowerToys.exe");
if (File.Exists(sameDirPath))
{
powertoysExe = sameDirPath;
}
if (powertoysExe == null)
{
return (1, "Error: PowerToys.exe not found. Please ensure PowerToys is installed.");
}
Process.Start(new ProcessStartInfo
{
FileName = powertoysExe,
Arguments = "--open-settings=FancyZones",
UseShellExecute = false,
});
return (0, "FancyZones Settings opened successfully.");
}
catch (Exception ex)
{
return (1, $"Error: Failed to open FancyZones Settings. {ex.Message}");
}
}
}

View File

@@ -0,0 +1,98 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
namespace FancyZonesCLI.Commands;
/// <summary>
/// Hotkey-related commands.
/// </summary>
internal static class HotkeyCommands
{
public static (int ExitCode, string Output) GetHotkeys()
{
var hotkeys = FancyZonesData.ReadLayoutHotkeys();
if (hotkeys?.Hotkeys == null || hotkeys.Hotkeys.Count == 0)
{
return (0, "No hotkeys configured.");
}
var sb = new System.Text.StringBuilder();
sb.AppendLine("=== Layout Hotkeys ===\n");
sb.AppendLine("Press Win + Ctrl + Alt + <number> to switch layouts:\n");
foreach (var hotkey in hotkeys.Hotkeys.OrderBy(h => h.Key))
{
sb.AppendLine(CultureInfo.InvariantCulture, $" [{hotkey.Key}] => {hotkey.LayoutId}");
}
return (0, sb.ToString().TrimEnd());
}
public static (int ExitCode, string Output) SetHotkey(int key, string layoutUuid, Action<uint> notifyFancyZones, uint wmPrivLayoutHotkeysFileUpdate)
{
if (key < 0 || key > 9)
{
return (1, "Error: Key must be between 0 and 9");
}
// Check if this is a custom layout UUID
var customLayouts = FancyZonesData.ReadCustomLayouts();
var matchedLayout = customLayouts?.Layouts?.FirstOrDefault(l => l.Uuid.Equals(layoutUuid, StringComparison.OrdinalIgnoreCase));
bool isCustomLayout = matchedLayout != null;
string layoutName = matchedLayout?.Name ?? layoutUuid;
var hotkeys = FancyZonesData.ReadLayoutHotkeys() ?? new LayoutHotkeys();
hotkeys.Hotkeys ??= new List<LayoutHotkey>();
// Remove existing hotkey for this key
hotkeys.Hotkeys.RemoveAll(h => h.Key == key);
// Add new hotkey
hotkeys.Hotkeys.Add(new LayoutHotkey { Key = key, LayoutId = layoutUuid });
// Save
FancyZonesData.WriteLayoutHotkeys(hotkeys);
// Notify FancyZones
notifyFancyZones(wmPrivLayoutHotkeysFileUpdate);
if (isCustomLayout)
{
return (0, $"✓ Hotkey {key} assigned to custom layout '{layoutName}'\n Press Win + Ctrl + Alt + {key} to switch to this layout");
}
else
{
return (0, $"⚠ Warning: Hotkey {key} assigned to '{layoutUuid}'\n Note: FancyZones hotkeys only work with CUSTOM layouts.\n Template layouts (focus, columns, rows, etc.) cannot be used with hotkeys.\n Create a custom layout in the FancyZones Editor to use this hotkey.");
}
}
public static (int ExitCode, string Output) RemoveHotkey(int key, Action<uint> notifyFancyZones, uint wmPrivLayoutHotkeysFileUpdate)
{
var hotkeys = FancyZonesData.ReadLayoutHotkeys();
if (hotkeys?.Hotkeys == null)
{
return (0, $"No hotkey assigned to key {key}");
}
var removed = hotkeys.Hotkeys.RemoveAll(h => h.Key == key);
if (removed == 0)
{
return (0, $"No hotkey assigned to key {key}");
}
// Save
FancyZonesData.WriteLayoutHotkeys(hotkeys);
// Notify FancyZones
notifyFancyZones(wmPrivLayoutHotkeysFileUpdate);
return (0, $"Hotkey {key} removed");
}
}

View File

@@ -0,0 +1,276 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text.Json;
namespace FancyZonesCLI.Commands;
/// <summary>
/// Layout-related commands.
/// </summary>
internal static class LayoutCommands
{
public static (int ExitCode, string Output) GetLayouts()
{
var sb = new System.Text.StringBuilder();
// Print template layouts
var templatesJson = FancyZonesData.ReadLayoutTemplates();
if (templatesJson?.Templates != null)
{
sb.AppendLine(CultureInfo.InvariantCulture, $"=== Built-in Template Layouts ({templatesJson.Templates.Count} total) ===\n");
for (int i = 0; i < templatesJson.Templates.Count; i++)
{
var template = templatesJson.Templates[i];
sb.AppendLine(CultureInfo.InvariantCulture, $"[T{i + 1}] {template.Type}");
sb.Append(CultureInfo.InvariantCulture, $" Zones: {template.ZoneCount}");
if (template.ShowSpacing && template.Spacing > 0)
{
sb.Append(CultureInfo.InvariantCulture, $", Spacing: {template.Spacing}px");
}
sb.AppendLine();
sb.AppendLine();
// Draw visual preview
sb.Append(LayoutVisualizer.DrawTemplateLayout(template));
if (i < templatesJson.Templates.Count - 1)
{
sb.AppendLine();
}
}
sb.AppendLine("\n");
}
// Print custom layouts
var customLayouts = FancyZonesData.ReadCustomLayouts();
if (customLayouts?.Layouts != null)
{
sb.AppendLine(CultureInfo.InvariantCulture, $"=== Custom Layouts ({customLayouts.Layouts.Count} total) ===");
for (int i = 0; i < customLayouts.Layouts.Count; i++)
{
var layout = customLayouts.Layouts[i];
sb.AppendLine(CultureInfo.InvariantCulture, $"[{i + 1}] {layout.Name}");
sb.AppendLine(CultureInfo.InvariantCulture, $" UUID: {layout.Uuid}");
sb.Append(CultureInfo.InvariantCulture, $" Type: {layout.Type}");
bool isCanvasLayout = false;
if (layout.Info.ValueKind != JsonValueKind.Undefined && layout.Info.ValueKind != JsonValueKind.Null)
{
if (layout.Type == "grid" && layout.Info.TryGetProperty("rows", out var rows) && layout.Info.TryGetProperty("columns", out var cols))
{
sb.Append(CultureInfo.InvariantCulture, $" ({rows.GetInt32()}x{cols.GetInt32()} grid)");
}
else if (layout.Type == "canvas" && layout.Info.TryGetProperty("zones", out var zones))
{
sb.Append(CultureInfo.InvariantCulture, $" ({zones.GetArrayLength()} zones)");
isCanvasLayout = true;
}
}
sb.AppendLine("\n");
// Draw visual preview
sb.Append(LayoutVisualizer.DrawCustomLayout(layout));
// Add note for canvas layouts
if (isCanvasLayout)
{
sb.AppendLine("\n Note: Canvas layout preview is approximate.");
sb.AppendLine(" Open FancyZones Editor for precise zone boundaries.");
}
if (i < customLayouts.Layouts.Count - 1)
{
sb.AppendLine();
}
}
sb.AppendLine("\nUse 'FancyZonesCLI.exe set-layout <UUID>' to apply a layout.");
}
return (0, sb.ToString().TrimEnd());
}
public static (int ExitCode, string Output) GetActiveLayout()
{
if (!FancyZonesData.TryReadAppliedLayouts(out var appliedLayouts, out var error))
{
return (1, $"Error: {error}");
}
if (appliedLayouts.Layouts == null || appliedLayouts.Layouts.Count == 0)
{
return (0, "No active layouts found.");
}
var sb = new System.Text.StringBuilder();
sb.AppendLine("\n=== Active FancyZones Layout(s) ===\n");
for (int i = 0; i < appliedLayouts.Layouts.Count; i++)
{
var layout = appliedLayouts.Layouts[i];
sb.AppendLine(CultureInfo.InvariantCulture, $"Monitor {i + 1}:");
sb.AppendLine(CultureInfo.InvariantCulture, $" Name: {layout.AppliedLayout.Type}");
sb.AppendLine(CultureInfo.InvariantCulture, $" UUID: {layout.AppliedLayout.Uuid}");
sb.AppendLine(CultureInfo.InvariantCulture, $" Type: {layout.AppliedLayout.Type} ({layout.AppliedLayout.ZoneCount} zones)");
if (layout.AppliedLayout.ShowSpacing)
{
sb.AppendLine(CultureInfo.InvariantCulture, $" Spacing: {layout.AppliedLayout.Spacing}px");
}
sb.AppendLine(CultureInfo.InvariantCulture, $" Sensitivity Radius: {layout.AppliedLayout.SensitivityRadius}px");
if (i < appliedLayouts.Layouts.Count - 1)
{
sb.AppendLine();
}
}
return (0, sb.ToString().TrimEnd());
}
public static (int ExitCode, string Output) SetLayout(string[] args, Action<uint> notifyFancyZones, uint wmPrivAppliedLayoutsFileUpdate)
{
Logger.LogInfo($"SetLayout called with args: [{string.Join(", ", args)}]");
if (args.Length == 0)
{
return (1, "Error: set-layout requires a UUID parameter");
}
string uuid = args[0];
int? targetMonitor = null;
bool applyToAll = false;
// Parse options
for (int i = 1; i < args.Length; i++)
{
if (args[i] == "--monitor" && i + 1 < args.Length)
{
if (int.TryParse(args[i + 1], out int monitorNum))
{
targetMonitor = monitorNum;
i++; // Skip next arg
}
else
{
return (1, $"Error: Invalid monitor number: {args[i + 1]}");
}
}
else if (args[i] == "--all")
{
applyToAll = true;
}
}
if (targetMonitor.HasValue && applyToAll)
{
return (1, "Error: Cannot specify both --monitor and --all");
}
// Try to find layout in custom layouts first (by UUID)
var customLayouts = FancyZonesData.ReadCustomLayouts();
var targetCustomLayout = customLayouts?.Layouts?.FirstOrDefault(l => l.Uuid.Equals(uuid, StringComparison.OrdinalIgnoreCase));
// If not found in custom layouts, try template layouts (by type name)
TemplateLayout targetTemplate = null;
if (targetCustomLayout == null)
{
var templates = FancyZonesData.ReadLayoutTemplates();
targetTemplate = templates?.Templates?.FirstOrDefault(t => t.Type.Equals(uuid, StringComparison.OrdinalIgnoreCase));
}
if (targetCustomLayout == null && targetTemplate == null)
{
return (1, $"Error: Layout '{uuid}' not found\nTip: For templates, use the type name (e.g., 'focus', 'columns', 'rows', 'grid', 'priority-grid')\n For custom layouts, use the UUID from 'get-layouts'");
}
// Read current applied layouts
if (!FancyZonesData.TryReadAppliedLayouts(out var appliedLayouts, out var error))
{
return (1, $"Error: {error}");
}
if (appliedLayouts.Layouts == null || appliedLayouts.Layouts.Count == 0)
{
return (1, "Error: No monitors configured");
}
// Determine which monitors to update
List<int> monitorsToUpdate = new List<int>();
if (applyToAll)
{
for (int i = 0; i < appliedLayouts.Layouts.Count; i++)
{
monitorsToUpdate.Add(i);
}
}
else if (targetMonitor.HasValue)
{
int monitorIndex = targetMonitor.Value - 1; // Convert to 0-based
if (monitorIndex < 0 || monitorIndex >= appliedLayouts.Layouts.Count)
{
return (1, $"Error: Monitor {targetMonitor.Value} not found. Available monitors: 1-{appliedLayouts.Layouts.Count}");
}
monitorsToUpdate.Add(monitorIndex);
}
else
{
// Default: first monitor
monitorsToUpdate.Add(0);
}
// Update selected monitors
foreach (int monitorIndex in monitorsToUpdate)
{
if (targetCustomLayout != null)
{
appliedLayouts.Layouts[monitorIndex].AppliedLayout.Uuid = targetCustomLayout.Uuid;
appliedLayouts.Layouts[monitorIndex].AppliedLayout.Type = targetCustomLayout.Type;
}
else if (targetTemplate != null)
{
// For templates, use all-zeros UUID and the template type
appliedLayouts.Layouts[monitorIndex].AppliedLayout.Uuid = "{00000000-0000-0000-0000-000000000000}";
appliedLayouts.Layouts[monitorIndex].AppliedLayout.Type = targetTemplate.Type;
appliedLayouts.Layouts[monitorIndex].AppliedLayout.ZoneCount = targetTemplate.ZoneCount;
appliedLayouts.Layouts[monitorIndex].AppliedLayout.ShowSpacing = targetTemplate.ShowSpacing;
appliedLayouts.Layouts[monitorIndex].AppliedLayout.Spacing = targetTemplate.Spacing;
}
}
// Write back to file
FancyZonesData.WriteAppliedLayouts(appliedLayouts);
Logger.LogInfo($"Applied layouts file updated for {monitorsToUpdate.Count} monitor(s)");
// Notify FancyZones to reload
notifyFancyZones(wmPrivAppliedLayoutsFileUpdate);
Logger.LogInfo("FancyZones notified of layout change");
string layoutName = targetCustomLayout?.Name ?? targetTemplate?.Type ?? uuid;
if (applyToAll)
{
return (0, $"Layout '{layoutName}' applied to all {monitorsToUpdate.Count} monitors");
}
else if (targetMonitor.HasValue)
{
return (0, $"Layout '{layoutName}' applied to monitor {targetMonitor.Value}");
}
else
{
return (0, $"Layout '{layoutName}' applied to monitor 1");
}
}
}

View File

@@ -0,0 +1,49 @@
// 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.Globalization;
namespace FancyZonesCLI.Commands;
/// <summary>
/// Monitor-related commands.
/// </summary>
internal static class MonitorCommands
{
public static (int ExitCode, string Output) GetMonitors()
{
if (!FancyZonesData.TryReadAppliedLayouts(out var appliedLayouts, out var error))
{
return (1, $"Error: {error}");
}
if (appliedLayouts.Layouts == null || appliedLayouts.Layouts.Count == 0)
{
return (0, "No monitors found.");
}
var sb = new System.Text.StringBuilder();
sb.AppendLine(CultureInfo.InvariantCulture, $"=== Monitors ({appliedLayouts.Layouts.Count} total) ===");
sb.AppendLine();
for (int i = 0; i < appliedLayouts.Layouts.Count; i++)
{
var layout = appliedLayouts.Layouts[i];
var monitorNum = i + 1;
sb.AppendLine(CultureInfo.InvariantCulture, $"Monitor {monitorNum}:");
sb.AppendLine(CultureInfo.InvariantCulture, $" Monitor: {layout.Device.Monitor}");
sb.AppendLine(CultureInfo.InvariantCulture, $" Monitor Instance: {layout.Device.MonitorInstance}");
sb.AppendLine(CultureInfo.InvariantCulture, $" Monitor Number: {layout.Device.MonitorNumber}");
sb.AppendLine(CultureInfo.InvariantCulture, $" Serial Number: {layout.Device.SerialNumber}");
sb.AppendLine(CultureInfo.InvariantCulture, $" Virtual Desktop: {layout.Device.VirtualDesktop}");
sb.AppendLine(CultureInfo.InvariantCulture, $" Sensitivity Radius: {layout.AppliedLayout.SensitivityRadius}px");
sb.AppendLine(CultureInfo.InvariantCulture, $" Active Layout: {layout.AppliedLayout.Type}");
sb.AppendLine(CultureInfo.InvariantCulture, $" Zone Count: {layout.AppliedLayout.ZoneCount}");
sb.AppendLine();
}
return (0, sb.ToString().TrimEnd());
}
}

View File

@@ -0,0 +1,32 @@
<Project Sdk="Microsoft.NET.Sdk">
<!-- Look at Directory.Build.props in root for common stuff as well -->
<Import Project="..\..\..\Common.Dotnet.CsWinRT.props" />
<Import Project="..\..\..\Common.SelfContained.props" />
<Import Project="..\..\..\Common.Dotnet.AotCompatibility.props" />
<PropertyGroup>
<AssemblyTitle>PowerToys.FancyZonesCLI</AssemblyTitle>
<AssemblyDescription>PowerToys FancyZones Command Line Interface</AssemblyDescription>
<Description>PowerToys FancyZones CLI</Description>
<OutputType>Exe</OutputType>
<Platforms>x64;ARM64</Platforms>
<PublishAot>true</PublishAot>
<DisableRuntimeMarshalling>true</DisableRuntimeMarshalling>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<OutputPath>..\..\..\..\$(Platform)\$(Configuration)</OutputPath>
<AssemblyName>FancyZonesCLI</AssemblyName>
<NoWarn>$(NoWarn);SA1500;SA1402;CA1852</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.Text.Json" />
<PackageReference Include="Microsoft.Windows.CsWin32" PrivateAssets="all" />
</ItemGroup>
<!-- Force using WindowsDesktop runtime to ensure consistent dll versions with other projects -->
<ItemGroup>
<FrameworkReference Include="Microsoft.WindowsDesktop.App" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,142 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.IO;
using System.Text.Json;
using System.Text.Json.Serialization.Metadata;
namespace FancyZonesCLI;
/// <summary>
/// Provides methods to read and write FancyZones configuration data.
/// </summary>
internal static class FancyZonesData
{
/// <summary>
/// Try to read applied layouts configuration.
/// </summary>
public static bool TryReadAppliedLayouts(out AppliedLayouts result, out string error)
{
return TryReadJsonFile(FancyZonesPaths.AppliedLayouts, FancyZonesJsonContext.Default.AppliedLayouts, out result, out error);
}
/// <summary>
/// Read applied layouts or return null if not found.
/// </summary>
public static AppliedLayouts ReadAppliedLayouts()
{
return ReadJsonFileOrDefault(FancyZonesPaths.AppliedLayouts, FancyZonesJsonContext.Default.AppliedLayouts);
}
/// <summary>
/// Write applied layouts configuration.
/// </summary>
public static void WriteAppliedLayouts(AppliedLayouts layouts)
{
WriteJsonFile(FancyZonesPaths.AppliedLayouts, layouts, FancyZonesJsonContext.Default.AppliedLayouts);
}
/// <summary>
/// Read custom layouts or return null if not found.
/// </summary>
public static CustomLayouts ReadCustomLayouts()
{
return ReadJsonFileOrDefault(FancyZonesPaths.CustomLayouts, FancyZonesJsonContext.Default.CustomLayouts);
}
/// <summary>
/// Read layout templates or return null if not found.
/// </summary>
public static LayoutTemplates ReadLayoutTemplates()
{
return ReadJsonFileOrDefault(FancyZonesPaths.LayoutTemplates, FancyZonesJsonContext.Default.LayoutTemplates);
}
/// <summary>
/// Read layout hotkeys or return null if not found.
/// </summary>
public static LayoutHotkeys ReadLayoutHotkeys()
{
return ReadJsonFileOrDefault(FancyZonesPaths.LayoutHotkeys, FancyZonesJsonContext.Default.LayoutHotkeys);
}
/// <summary>
/// Write layout hotkeys configuration.
/// </summary>
public static void WriteLayoutHotkeys(LayoutHotkeys hotkeys)
{
WriteJsonFile(FancyZonesPaths.LayoutHotkeys, hotkeys, FancyZonesJsonContext.Default.LayoutHotkeys);
}
/// <summary>
/// Check if editor parameters file exists.
/// </summary>
public static bool EditorParametersExist()
{
return File.Exists(FancyZonesPaths.EditorParameters);
}
private static bool TryReadJsonFile<T>(string filePath, JsonTypeInfo<T> jsonTypeInfo, out T result, out string error)
where T : class
{
result = null;
error = null;
Logger.LogDebug($"Reading file: {filePath}");
if (!File.Exists(filePath))
{
error = $"File not found: {Path.GetFileName(filePath)}";
Logger.LogWarning(error);
return false;
}
try
{
var json = File.ReadAllText(filePath);
result = JsonSerializer.Deserialize(json, jsonTypeInfo);
if (result == null)
{
error = $"Failed to parse {Path.GetFileName(filePath)}";
Logger.LogError(error);
return false;
}
Logger.LogDebug($"Successfully read {Path.GetFileName(filePath)}");
return true;
}
catch (JsonException ex)
{
error = $"JSON parse error in {Path.GetFileName(filePath)}: {ex.Message}";
Logger.LogError(error, ex);
return false;
}
catch (IOException ex)
{
error = $"Failed to read {Path.GetFileName(filePath)}: {ex.Message}";
Logger.LogError(error, ex);
return false;
}
}
private static T ReadJsonFileOrDefault<T>(string filePath, JsonTypeInfo<T> jsonTypeInfo, T defaultValue = null)
where T : class
{
if (TryReadJsonFile(filePath, jsonTypeInfo, out var result, out _))
{
return result;
}
return defaultValue;
}
private static void WriteJsonFile<T>(string filePath, T data, JsonTypeInfo<T> jsonTypeInfo)
{
Logger.LogDebug($"Writing file: {filePath}");
var json = JsonSerializer.Serialize(data, jsonTypeInfo);
File.WriteAllText(filePath, json);
Logger.LogInfo($"Successfully wrote {Path.GetFileName(filePath)}");
}
}

View File

@@ -0,0 +1,30 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.IO;
namespace FancyZonesCLI;
/// <summary>
/// Provides paths to FancyZones configuration files.
/// </summary>
internal static class FancyZonesPaths
{
private static readonly string DataPath = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
"Microsoft",
"PowerToys",
"FancyZones");
public static string AppliedLayouts => Path.Combine(DataPath, "applied-layouts.json");
public static string CustomLayouts => Path.Combine(DataPath, "custom-layouts.json");
public static string LayoutTemplates => Path.Combine(DataPath, "layout-templates.json");
public static string LayoutHotkeys => Path.Combine(DataPath, "layout-hotkeys.json");
public static string EditorParameters => Path.Combine(DataPath, "editor-parameters.json");
}

View File

@@ -0,0 +1,550 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using System.Text.Json;
namespace FancyZonesCLI;
public static class LayoutVisualizer
{
public static string DrawTemplateLayout(TemplateLayout template)
{
var sb = new StringBuilder();
sb.AppendLine(" Visual Preview:");
switch (template.Type.ToLowerInvariant())
{
case "focus":
sb.Append(RenderFocusLayout(template.ZoneCount > 0 ? template.ZoneCount : 3));
break;
case "columns":
sb.Append(RenderGridLayout(1, template.ZoneCount > 0 ? template.ZoneCount : 3));
break;
case "rows":
sb.Append(RenderGridLayout(template.ZoneCount > 0 ? template.ZoneCount : 3, 1));
break;
case "grid":
// Grid layout: calculate rows and columns from zone count
// Algorithm from GridLayoutModel.InitGrid() - tries to make it close to square
// with cols >= rows preference
int zoneCount = template.ZoneCount > 0 ? template.ZoneCount : 3;
int rows = 1;
while (zoneCount / rows >= rows)
{
rows++;
}
rows--;
int cols = zoneCount / rows;
if (zoneCount % rows != 0)
{
cols++;
}
sb.Append(RenderGridLayoutWithZoneCount(rows, cols, zoneCount));
break;
case "priority-grid":
sb.Append(RenderPriorityGridLayout(template.ZoneCount > 0 ? template.ZoneCount : 3));
break;
case "blank":
sb.AppendLine(" (No zones)");
break;
default:
sb.AppendLine(CultureInfo.InvariantCulture, $" ({template.Type} layout)");
break;
}
return sb.ToString();
}
public static string DrawCustomLayout(CustomLayout layout)
{
if (layout.Info.ValueKind == JsonValueKind.Undefined || layout.Info.ValueKind == JsonValueKind.Null)
{
return string.Empty;
}
var sb = new StringBuilder();
sb.AppendLine(" Visual Preview:");
if (layout.Type == "grid" &&
layout.Info.TryGetProperty("rows", out var rows) &&
layout.Info.TryGetProperty("columns", out var cols))
{
int r = rows.GetInt32();
int c = cols.GetInt32();
// Check if there's a cell-child-map (merged cells)
if (layout.Info.TryGetProperty("cell-child-map", out var cellMap))
{
sb.Append(RenderGridLayoutWithMergedCells(r, c, cellMap));
}
else
{
int height = r >= 4 ? 12 : 8;
sb.Append(RenderGridLayout(r, c, 30, height));
}
}
else if (layout.Type == "canvas" &&
layout.Info.TryGetProperty("zones", out var zones) &&
layout.Info.TryGetProperty("ref-width", out var refWidth) &&
layout.Info.TryGetProperty("ref-height", out var refHeight))
{
sb.Append(RenderCanvasLayout(zones, refWidth.GetInt32(), refHeight.GetInt32()));
}
return sb.ToString();
}
private static string RenderFocusLayout(int zoneCount = 3)
{
var sb = new StringBuilder();
// Focus layout: overlapping zones with cascading offset
if (zoneCount == 1)
{
sb.AppendLine(" +-------+");
sb.AppendLine(" | |");
sb.AppendLine(" | |");
sb.AppendLine(" +-------+");
}
else if (zoneCount == 2)
{
sb.AppendLine(" +-------+");
sb.AppendLine(" | |");
sb.AppendLine(" | +-------+");
sb.AppendLine(" +-| |");
sb.AppendLine(" | |");
sb.AppendLine(" +-------+");
}
else
{
sb.AppendLine(" +-------+");
sb.AppendLine(" | |");
sb.AppendLine(" | +-------+");
sb.AppendLine(" +-| |");
sb.AppendLine(" | +-------+");
sb.AppendLine(" +-| |");
sb.AppendLine(" ...");
sb.AppendLine(CultureInfo.InvariantCulture, $" (total: {zoneCount} zones)");
sb.AppendLine(" ...");
sb.AppendLine(" | +-------+");
sb.AppendLine(" +-| |");
sb.AppendLine(" | |");
sb.AppendLine(" +-------+");
}
return sb.ToString();
}
private static string RenderPriorityGridLayout(int zoneCount = 3)
{
// Priority Grid has predefined layouts for zone counts 1-11
// Data format from GridLayoutModel._priorityData
if (zoneCount >= 1 && zoneCount <= 11)
{
int[,] cellMap = GetPriorityGridCellMap(zoneCount);
return RenderGridLayoutWithCellMap(cellMap);
}
else
{
// > 11 zones: use grid layout
int rows = 1;
while (zoneCount / rows >= rows)
{
rows++;
}
rows--;
int cols = zoneCount / rows;
if (zoneCount % rows != 0)
{
cols++;
}
return RenderGridLayoutWithZoneCount(rows, cols, zoneCount);
}
}
private static int[,] GetPriorityGridCellMap(int zoneCount)
{
// Parsed from Editor's _priorityData byte arrays
return zoneCount switch
{
1 => new int[,] { { 0 } },
2 => new int[,] { { 0, 1 } },
3 => new int[,] { { 0, 1, 2 } },
4 => new int[,] { { 0, 1, 2 }, { 0, 1, 3 } },
5 => new int[,] { { 0, 1, 2 }, { 3, 1, 4 } },
6 => new int[,] { { 0, 1, 2 }, { 0, 1, 3 }, { 4, 1, 5 } },
7 => new int[,] { { 0, 1, 2 }, { 3, 1, 4 }, { 5, 1, 6 } },
8 => new int[,] { { 0, 1, 2, 3 }, { 4, 1, 2, 5 }, { 6, 1, 2, 7 } },
9 => new int[,] { { 0, 1, 2, 3 }, { 4, 1, 2, 5 }, { 6, 1, 7, 8 } },
10 => new int[,] { { 0, 1, 2, 3 }, { 4, 1, 5, 6 }, { 7, 1, 8, 9 } },
11 => new int[,] { { 0, 1, 2, 3 }, { 4, 1, 5, 6 }, { 7, 8, 9, 10 } },
_ => new int[,] { { 0 } },
};
}
private static string RenderGridLayoutWithCellMap(int[,] cellMap, int width = 30, int height = 8)
{
var sb = new StringBuilder();
int rows = cellMap.GetLength(0);
int cols = cellMap.GetLength(1);
int cellWidth = width / cols;
int cellHeight = height / rows;
for (int r = 0; r < rows; r++)
{
// Top border
sb.Append(" +");
for (int c = 0; c < cols; c++)
{
bool mergeTop = r > 0 && cellMap[r, c] == cellMap[r - 1, c];
bool mergeLeft = c > 0 && cellMap[r, c] == cellMap[r, c - 1];
if (mergeTop)
{
sb.Append(mergeLeft ? new string(' ', cellWidth) : new string(' ', cellWidth - 1) + "+");
}
else
{
sb.Append(mergeLeft ? new string('-', cellWidth) : new string('-', cellWidth - 1) + "+");
}
}
sb.AppendLine();
// Cell content
for (int h = 0; h < cellHeight - 1; h++)
{
sb.Append(" ");
for (int c = 0; c < cols; c++)
{
bool mergeLeft = c > 0 && cellMap[r, c] == cellMap[r, c - 1];
sb.Append(mergeLeft ? ' ' : '|');
sb.Append(' ', cellWidth - 1);
}
sb.AppendLine("|");
}
}
// Bottom border
sb.Append(" +");
for (int c = 0; c < cols; c++)
{
sb.Append('-', cellWidth - 1);
sb.Append('+');
}
sb.AppendLine();
return sb.ToString();
}
private static string RenderGridLayoutWithMergedCells(int rows, int cols, JsonElement cellMap)
{
var sb = new StringBuilder();
const int displayWidth = 39;
const int displayHeight = 12;
// Build zone map from cell-child-map
int[,] zoneMap = new int[rows, cols];
for (int r = 0; r < rows; r++)
{
var rowArray = cellMap[r];
for (int c = 0; c < cols; c++)
{
zoneMap[r, c] = rowArray[c].GetInt32();
}
}
int cellHeight = displayHeight / rows;
int cellWidth = displayWidth / cols;
// Draw top border
sb.Append(" +");
sb.Append('-', displayWidth);
sb.AppendLine("+");
// Draw rows
for (int r = 0; r < rows; r++)
{
for (int h = 0; h < cellHeight; h++)
{
sb.Append(" |");
for (int c = 0; c < cols; c++)
{
int currentZone = zoneMap[r, c];
int leftZone = c > 0 ? zoneMap[r, c - 1] : -1;
bool needLeftBorder = c > 0 && currentZone != leftZone;
bool zoneHasTopBorder = r > 0 && h == 0 && currentZone != zoneMap[r - 1, c];
if (needLeftBorder)
{
sb.Append('|');
sb.Append(zoneHasTopBorder ? '-' : ' ', cellWidth - 1);
}
else
{
sb.Append(zoneHasTopBorder ? '-' : ' ', cellWidth);
}
}
sb.AppendLine("|");
}
}
// Draw bottom border
sb.Append(" +");
sb.Append('-', displayWidth);
sb.AppendLine("+");
return sb.ToString();
}
public static string RenderGridLayoutWithZoneCount(int rows, int cols, int zoneCount, int width = 30, int height = 8)
{
var sb = new StringBuilder();
// Build zone map like Editor's InitGrid
int[,] zoneMap = new int[rows, cols];
int index = 0;
for (int r = 0; r < rows; r++)
{
for (int c = 0; c < cols; c++)
{
zoneMap[r, c] = index++;
if (index == zoneCount)
{
index--; // Remaining cells use the last zone index
}
}
}
int cellWidth = width / cols;
int cellHeight = height / rows;
for (int r = 0; r < rows; r++)
{
// Top border
sb.Append(" +");
for (int c = 0; c < cols; c++)
{
bool mergeLeft = c > 0 && zoneMap[r, c] == zoneMap[r, c - 1];
sb.Append('-', mergeLeft ? cellWidth : cellWidth - 1);
if (!mergeLeft)
{
sb.Append('+');
}
}
sb.AppendLine();
// Cell content
for (int h = 0; h < cellHeight - 1; h++)
{
sb.Append(" ");
for (int c = 0; c < cols; c++)
{
bool mergeLeft = c > 0 && zoneMap[r, c] == zoneMap[r, c - 1];
sb.Append(mergeLeft ? ' ' : '|');
sb.Append(' ', cellWidth - 1);
}
sb.AppendLine("|");
}
}
// Bottom border
sb.Append(" +");
for (int c = 0; c < cols; c++)
{
sb.Append('-', cellWidth - 1);
sb.Append('+');
}
sb.AppendLine();
return sb.ToString();
}
public static string RenderGridLayout(int rows, int cols, int width = 30, int height = 8)
{
var sb = new StringBuilder();
int cellWidth = width / cols;
int cellHeight = height / rows;
for (int r = 0; r < rows; r++)
{
// Top border
sb.Append(" +");
for (int c = 0; c < cols; c++)
{
sb.Append('-', cellWidth - 1);
sb.Append('+');
}
sb.AppendLine();
// Cell content
for (int h = 0; h < cellHeight - 1; h++)
{
sb.Append(" ");
for (int c = 0; c < cols; c++)
{
sb.Append('|');
sb.Append(' ', cellWidth - 1);
}
sb.AppendLine("|");
}
}
// Bottom border
sb.Append(" +");
for (int c = 0; c < cols; c++)
{
sb.Append('-', cellWidth - 1);
sb.Append('+');
}
sb.AppendLine();
return sb.ToString();
}
private static string RenderCanvasLayout(JsonElement zones, int refWidth, int refHeight)
{
var sb = new StringBuilder();
const int displayWidth = 49;
const int displayHeight = 15;
// Create a 2D array to track which zones occupy each position
var zoneGrid = new List<int>[displayHeight, displayWidth];
for (int i = 0; i < displayHeight; i++)
{
for (int j = 0; j < displayWidth; j++)
{
zoneGrid[i, j] = new List<int>();
}
}
// Map each zone to the grid
int zoneId = 0;
var zoneList = new List<(int X, int Y, int Width, int Height, int Id)>();
foreach (var zone in zones.EnumerateArray())
{
int x = zone.GetProperty("X").GetInt32();
int y = zone.GetProperty("Y").GetInt32();
int w = zone.GetProperty("width").GetInt32();
int h = zone.GetProperty("height").GetInt32();
int dx = Math.Max(0, Math.Min(displayWidth - 1, x * displayWidth / refWidth));
int dy = Math.Max(0, Math.Min(displayHeight - 1, y * displayHeight / refHeight));
int dw = Math.Max(3, w * displayWidth / refWidth);
int dh = Math.Max(2, h * displayHeight / refHeight);
if (dx + dw > displayWidth)
{
dw = displayWidth - dx;
}
if (dy + dh > displayHeight)
{
dh = displayHeight - dy;
}
zoneList.Add((dx, dy, dw, dh, zoneId));
for (int r = dy; r < dy + dh && r < displayHeight; r++)
{
for (int c = dx; c < dx + dw && c < displayWidth; c++)
{
zoneGrid[r, c].Add(zoneId);
}
}
zoneId++;
}
// Draw top border
sb.Append(" +");
sb.Append('-', displayWidth);
sb.AppendLine("+");
// Draw each row
char[] shades = { '.', ':', '░', '▒', '▓', '█', '◆', '●', '■', '▪' };
for (int r = 0; r < displayHeight; r++)
{
sb.Append(" |");
for (int c = 0; c < displayWidth; c++)
{
var zonesHere = zoneGrid[r, c];
if (zonesHere.Count == 0)
{
sb.Append(' ');
}
else
{
int topZone = zonesHere[zonesHere.Count - 1];
var rect = zoneList[topZone];
bool isTopEdge = r == rect.Y;
bool isBottomEdge = r == rect.Y + rect.Height - 1;
bool isLeftEdge = c == rect.X;
bool isRightEdge = c == rect.X + rect.Width - 1;
if ((isTopEdge || isBottomEdge) && (isLeftEdge || isRightEdge))
{
sb.Append('+');
}
else if (isTopEdge || isBottomEdge)
{
sb.Append('-');
}
else if (isLeftEdge || isRightEdge)
{
sb.Append('|');
}
else
{
sb.Append(shades[topZone % shades.Length]);
}
}
}
sb.AppendLine("|");
}
// Draw bottom border
sb.Append(" +");
sb.Append('-', displayWidth);
sb.AppendLine("+");
// Draw legend
sb.AppendLine();
sb.Append(" Legend: ");
for (int i = 0; i < Math.Min(zoneId, shades.Length); i++)
{
if (i > 0)
{
sb.Append(", ");
}
sb.Append(CultureInfo.InvariantCulture, $"Zone {i} = {shades[i]}");
}
sb.AppendLine();
return sb.ToString();
}
}

View File

@@ -0,0 +1,126 @@
// 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.Globalization;
using System.IO;
using System.Runtime.CompilerServices;
namespace FancyZonesCLI;
/// <summary>
/// Simple logger for FancyZones CLI.
/// Logs to %LOCALAPPDATA%\Microsoft\PowerToys\FancyZones\CLI\Logs
/// </summary>
internal static class Logger
{
private static readonly object LockObj = new();
private static string _logFilePath = string.Empty;
private static bool _isInitialized;
/// <summary>
/// Gets the path to the current log file.
/// </summary>
public static string LogFilePath => _logFilePath;
/// <summary>
/// Initializes the logger.
/// </summary>
public static void InitializeLogger()
{
if (_isInitialized)
{
return;
}
try
{
var localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
var logDirectory = Path.Combine(localAppData, "Microsoft", "PowerToys", "FancyZones", "CLI", "Logs");
if (!Directory.Exists(logDirectory))
{
Directory.CreateDirectory(logDirectory);
}
var logFileName = $"FancyZonesCLI_{DateTime.Now:yyyy-MM-dd}.log";
_logFilePath = Path.Combine(logDirectory, logFileName);
_isInitialized = true;
LogInfo("FancyZones CLI started");
}
catch
{
// Silently fail if logging cannot be initialized
}
}
/// <summary>
/// Logs an error message.
/// </summary>
public static void LogError(string message, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0)
{
Log("ERROR", message, memberName, sourceFilePath, sourceLineNumber);
}
/// <summary>
/// Logs an error message with exception details.
/// </summary>
public static void LogError(string message, Exception ex, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0)
{
var fullMessage = ex == null
? message
: $"{message} | Exception: {ex.GetType().Name}: {ex.Message}";
Log("ERROR", fullMessage, memberName, sourceFilePath, sourceLineNumber);
}
/// <summary>
/// Logs a warning message.
/// </summary>
public static void LogWarning(string message, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0)
{
Log("WARN", message, memberName, sourceFilePath, sourceLineNumber);
}
/// <summary>
/// Logs an informational message.
/// </summary>
public static void LogInfo(string message, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0)
{
Log("INFO", message, memberName, sourceFilePath, sourceLineNumber);
}
/// <summary>
/// Logs a debug message (only in DEBUG builds).
/// </summary>
[System.Diagnostics.Conditional("DEBUG")]
public static void LogDebug(string message, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0)
{
Log("DEBUG", message, memberName, sourceFilePath, sourceLineNumber);
}
private static void Log(string level, string message, string memberName, string sourceFilePath, int sourceLineNumber)
{
if (!_isInitialized || string.IsNullOrEmpty(_logFilePath))
{
return;
}
try
{
var fileName = Path.GetFileName(sourceFilePath);
var timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff", CultureInfo.InvariantCulture);
var logEntry = $"[{timestamp}] [{level}] [{fileName}:{sourceLineNumber}] [{memberName}] {message}{Environment.NewLine}";
lock (LockObj)
{
File.AppendAllText(_logFilePath, logEntry);
}
}
catch
{
// Silently fail if logging fails
}
}
}

View File

@@ -0,0 +1,137 @@
// 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.Collections.Generic;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace FancyZonesCLI;
// JSON Source Generator for AOT compatibility
[JsonSerializable(typeof(LayoutTemplates))]
[JsonSerializable(typeof(CustomLayouts))]
[JsonSerializable(typeof(AppliedLayouts))]
[JsonSerializable(typeof(LayoutHotkeys))]
[JsonSourceGenerationOptions(WriteIndented = true)]
internal partial class FancyZonesJsonContext : JsonSerializerContext
{
}
// Layout Templates
public sealed class LayoutTemplates
{
[JsonPropertyName("layout-templates")]
public List<TemplateLayout> Templates { get; set; }
}
public sealed class TemplateLayout
{
[JsonPropertyName("type")]
public string Type { get; set; } = string.Empty;
[JsonPropertyName("zone-count")]
public int ZoneCount { get; set; }
[JsonPropertyName("show-spacing")]
public bool ShowSpacing { get; set; }
[JsonPropertyName("spacing")]
public int Spacing { get; set; }
[JsonPropertyName("sensitivity-radius")]
public int SensitivityRadius { get; set; }
}
// Custom Layouts
public sealed class CustomLayouts
{
[JsonPropertyName("custom-layouts")]
public List<CustomLayout> Layouts { get; set; }
}
public sealed class CustomLayout
{
[JsonPropertyName("uuid")]
public string Uuid { get; set; } = string.Empty;
[JsonPropertyName("name")]
public string Name { get; set; } = string.Empty;
[JsonPropertyName("type")]
public string Type { get; set; } = string.Empty;
[JsonPropertyName("info")]
public JsonElement Info { get; set; }
}
// Applied Layouts
public sealed class AppliedLayouts
{
[JsonPropertyName("applied-layouts")]
public List<AppliedLayoutWrapper> Layouts { get; set; }
}
public sealed class AppliedLayoutWrapper
{
[JsonPropertyName("device")]
public DeviceInfo Device { get; set; } = new();
[JsonPropertyName("applied-layout")]
public AppliedLayoutInfo AppliedLayout { get; set; } = new();
}
public sealed class DeviceInfo
{
[JsonPropertyName("monitor")]
public string Monitor { get; set; } = string.Empty;
[JsonPropertyName("monitor-instance")]
public string MonitorInstance { get; set; } = string.Empty;
[JsonPropertyName("monitor-number")]
public int MonitorNumber { get; set; }
[JsonPropertyName("serial-number")]
public string SerialNumber { get; set; } = string.Empty;
[JsonPropertyName("virtual-desktop")]
public string VirtualDesktop { get; set; } = string.Empty;
}
public sealed class AppliedLayoutInfo
{
[JsonPropertyName("uuid")]
public string Uuid { get; set; } = string.Empty;
[JsonPropertyName("type")]
public string Type { get; set; } = string.Empty;
[JsonPropertyName("show-spacing")]
public bool ShowSpacing { get; set; }
[JsonPropertyName("spacing")]
public int Spacing { get; set; }
[JsonPropertyName("zone-count")]
public int ZoneCount { get; set; }
[JsonPropertyName("sensitivity-radius")]
public int SensitivityRadius { get; set; }
}
// Layout Hotkeys
public sealed class LayoutHotkeys
{
[JsonPropertyName("layout-hotkeys")]
public List<LayoutHotkey> Hotkeys { get; set; }
}
public sealed class LayoutHotkey
{
[JsonPropertyName("key")]
public int Key { get; set; }
[JsonPropertyName("layout-id")]
public string LayoutId { get; set; } = string.Empty;
}

View File

@@ -0,0 +1,56 @@
// 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 Windows.Win32;
using Windows.Win32.Foundation;
namespace FancyZonesCLI;
/// <summary>
/// Native Windows API methods for FancyZones CLI.
/// </summary>
internal static class NativeMethods
{
// Registered Windows messages for notifying FancyZones
private static uint wmPrivAppliedLayoutsFileUpdate;
private static uint wmPrivLayoutHotkeysFileUpdate;
/// <summary>
/// Gets the Windows message ID for applied layouts file update notification.
/// </summary>
public static uint WM_PRIV_APPLIED_LAYOUTS_FILE_UPDATE => wmPrivAppliedLayoutsFileUpdate;
/// <summary>
/// Gets the Windows message ID for layout hotkeys file update notification.
/// </summary>
public static uint WM_PRIV_LAYOUT_HOTKEYS_FILE_UPDATE => wmPrivLayoutHotkeysFileUpdate;
/// <summary>
/// Initializes the Windows messages used for FancyZones notifications.
/// </summary>
public static void InitializeWindowMessages()
{
wmPrivAppliedLayoutsFileUpdate = PInvoke.RegisterWindowMessage("{2ef2c8a7-e0d5-4f31-9ede-52aade2d284d}");
wmPrivLayoutHotkeysFileUpdate = PInvoke.RegisterWindowMessage("{07229b7e-4f22-4357-b136-33c289be2295}");
}
/// <summary>
/// Broadcasts a notification message to FancyZones.
/// </summary>
/// <param name="message">The Windows message ID to broadcast.</param>
public static void NotifyFancyZones(uint message)
{
PInvoke.PostMessage(HWND.HWND_BROADCAST, message, 0, 0);
}
/// <summary>
/// Brings the specified window to the foreground.
/// </summary>
/// <param name="hWnd">A handle to the window.</param>
/// <returns>True if the window was brought to the foreground.</returns>
public static bool SetForegroundWindow(nint hWnd)
{
return PInvoke.SetForegroundWindow(new HWND(hWnd));
}
}

View File

@@ -0,0 +1,5 @@
{
"$schema": "https://aka.ms/CsWin32.schema.json",
"emitSingleFile": true,
"allowMarshaling": false
}

View File

@@ -0,0 +1,4 @@
PostMessage
SetForegroundWindow
RegisterWindowMessage
HWND_BROADCAST

View File

@@ -0,0 +1,115 @@
// 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.Globalization;
using System.Linq;
using FancyZonesCLI.Commands;
namespace FancyZonesCLI;
internal sealed class Program
{
private static int Main(string[] args)
{
// Initialize logger
Logger.InitializeLogger();
Logger.LogInfo($"CLI invoked with args: [{string.Join(", ", args)}]");
// Initialize Windows messages
NativeMethods.InitializeWindowMessages();
(int ExitCode, string Output) result;
if (args.Length == 0)
{
result = (1, GetUsageText());
}
else
{
var command = args[0].ToLowerInvariant();
result = command switch
{
"open-editor" or "editor" or "e" => EditorCommands.OpenEditor(),
"get-monitors" or "monitors" or "m" => MonitorCommands.GetMonitors(),
"get-layouts" or "layouts" or "ls" => LayoutCommands.GetLayouts(),
"get-active-layout" or "active" or "get-active" or "a" => LayoutCommands.GetActiveLayout(),
"set-layout" or "set" or "s" => args.Length >= 2
? LayoutCommands.SetLayout(args.Skip(1).ToArray(), NativeMethods.NotifyFancyZones, NativeMethods.WM_PRIV_APPLIED_LAYOUTS_FILE_UPDATE)
: (1, "Error: set-layout requires a UUID parameter"),
"open-settings" or "settings" => EditorCommands.OpenSettings(),
"get-hotkeys" or "hotkeys" or "hk" => HotkeyCommands.GetHotkeys(),
"set-hotkey" or "shk" => args.Length >= 3
? HotkeyCommands.SetHotkey(int.Parse(args[1], CultureInfo.InvariantCulture), args[2], NativeMethods.NotifyFancyZones, NativeMethods.WM_PRIV_LAYOUT_HOTKEYS_FILE_UPDATE)
: (1, "Error: set-hotkey requires <key> <uuid>"),
"remove-hotkey" or "rhk" => args.Length >= 2
? HotkeyCommands.RemoveHotkey(int.Parse(args[1], CultureInfo.InvariantCulture), NativeMethods.NotifyFancyZones, NativeMethods.WM_PRIV_LAYOUT_HOTKEYS_FILE_UPDATE)
: (1, "Error: remove-hotkey requires <key>"),
"help" or "--help" or "-h" => (0, GetUsageText()),
_ => (1, $"Error: Unknown command: {command}\n\n{GetUsageText()}"),
};
}
// Log result
if (result.ExitCode == 0)
{
Logger.LogInfo($"Command completed successfully");
}
else
{
Logger.LogWarning($"Command failed with exit code {result.ExitCode}: {result.Output}");
}
// Output result
if (!string.IsNullOrEmpty(result.Output))
{
Console.WriteLine(result.Output);
}
return result.ExitCode;
}
private static string GetUsageText()
{
return """
FancyZones CLI - Command line interface for FancyZones
======================================================
Usage: FancyZonesCLI.exe <command> [options]
Commands:
open-editor (editor, e) Launch FancyZones layout editor
get-monitors (monitors, m) List all monitors and their properties
get-layouts (layouts, ls) List all available layouts
get-active-layout (get-active, active, a)
Show currently active layout
set-layout (set, s) <uuid> [options]
Set layout by UUID
--monitor <n> Apply to monitor N (1-based)
--all Apply to all monitors
open-settings (settings) Open FancyZones settings page
get-hotkeys (hotkeys, hk) List all layout hotkeys
set-hotkey (shk) <key> <uuid> Assign hotkey (0-9) to CUSTOM layout
Note: Only custom layouts work with hotkeys
remove-hotkey (rhk) <key> Remove hotkey assignment
help Show this help message
Examples:
FancyZonesCLI.exe e # Open editor (short)
FancyZonesCLI.exe m # List monitors (short)
FancyZonesCLI.exe ls # List layouts (short)
FancyZonesCLI.exe a # Get active layout (short)
FancyZonesCLI.exe s focus --all # Set layout (short)
FancyZonesCLI.exe open-editor # Open editor (long)
FancyZonesCLI.exe get-monitors
FancyZonesCLI.exe get-layouts
FancyZonesCLI.exe set-layout {12345678-1234-1234-1234-123456789012}
FancyZonesCLI.exe set-layout focus --monitor 2
FancyZonesCLI.exe set-layout columns --all
FancyZonesCLI.exe set-hotkey 3 {12345678-1234-1234-1234-123456789012}
""";
}
}

View File

@@ -27,7 +27,7 @@ namespace Microsoft.PowerToys.Run.Plugin.PowerToys
public UtilityProvider()
{
var settingsUtils = new SettingsUtils();
var settingsUtils = SettingsUtils.Default;
var generalSettings = settingsUtils.GetSettings<GeneralSettings>();
_utilities = new List<Utility>();
@@ -228,7 +228,7 @@ namespace Microsoft.PowerToys.Run.Plugin.PowerToys
{
retryCount++;
var settingsUtils = new SettingsUtils();
var settingsUtils = SettingsUtils.Default;
var generalSettings = settingsUtils.GetSettings<GeneralSettings>();
foreach (var u in _utilities)

View File

@@ -38,7 +38,7 @@ namespace PowerLauncher
public SettingsReader(PowerToysRunSettings settings, ThemeManager themeManager)
{
_settingsUtils = new SettingsUtils();
_settingsUtils = SettingsUtils.Default;
_settings = settings;
_themeManager = themeManager;

View File

@@ -34,7 +34,7 @@ namespace Peek.FilePreviewer.Models
public PreviewSettings()
{
_settingsUtils = new SettingsUtils();
_settingsUtils = SettingsUtils.Default;
SourceCodeWrapText = false;
SourceCodeTryFormat = false;
SourceCodeFontSize = 14;

View File

@@ -77,7 +77,7 @@ namespace Peek.UI
public UserSettings()
{
_settingsUtils = new SettingsUtils();
_settingsUtils = SettingsUtils.Default;
LoadSettingsFromJson();

View File

@@ -24,7 +24,7 @@ public class SettingsService
public SettingsService(KeyboardListener keyboardListener)
{
_settingsUtils = new SettingsUtils();
_settingsUtils = SettingsUtils.Default;
_keyboardListener = keyboardListener;
ReadSettings();
_watcher = Helper.GetFileWatcher(PowerAccentModuleName, "settings.json", () => { ReadSettings(); });

View File

@@ -19,7 +19,7 @@ namespace PowerAccent.Core.Tools
public CharactersUsageInfo()
{
_filePath = new SettingsUtils().GetSettingsFilePath(PowerAccentSettings.ModuleName, "UsageInfo.json");
_filePath = SettingsUtils.Default.GetSettingsFilePath(PowerAccentSettings.ModuleName, "UsageInfo.json");
var data = GetUsageInfoData();
_characterUsageCounters = data.CharacterUsageCounters;
_characterUsageTimestamp = data.CharacterUsageTimestamp;

View File

@@ -36,6 +36,7 @@
<!-- MRT from windows app sdk will search for a pri file with the same name of the module before defaulting to resources.pri -->
<ProjectPriFileName>PowerToys.PowerRename.pri</ProjectPriFileName>
<RuntimeIdentifier>win10-x64;win10-arm64</RuntimeIdentifier>
<WindowsAppSDKVerifyTransitiveDependencies>false</WindowsAppSDKVerifyTransitiveDependencies>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.WindowsAppSDK" GeneratePathProperty="true" />

View File

@@ -16,7 +16,7 @@ namespace Microsoft.PowerToys.PreviewHandler.Monaco
/// </summary>
public class Settings
{
private static SettingsUtils moduleSettings = new SettingsUtils();
private static SettingsUtils moduleSettings = SettingsUtils.Default;
/// <summary>
/// Gets a value indicating whether word wrapping should be applied. Set by PT settings.

View File

@@ -155,7 +155,7 @@ namespace Microsoft.PowerToys.ThumbnailHandler.Stl
{
try
{
var moduleSettings = new SettingsUtils();
var moduleSettings = SettingsUtils.Default;
var colorString = moduleSettings.GetSettings<PowerPreviewSettings>(PowerPreviewSettings.ModuleName).Properties.StlThumbnailColor.Value;

View File

@@ -8,7 +8,7 @@ namespace SvgPreviewHandler
{
internal sealed class Settings
{
private static readonly SettingsUtils ModuleSettings = new SettingsUtils();
private static readonly SettingsUtils ModuleSettings = SettingsUtils.Default;
public int ColorMode
{

View File

@@ -592,7 +592,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
/// </summary>
public (bool Success, string Message, string Severity, bool LastBackupExists, string OptionalMessage) DryRunBackup()
{
var settingsUtils = new SettingsUtils();
var settingsUtils = SettingsUtils.Default;
var appBasePath = Path.GetDirectoryName(settingsUtils.GetSettingsFilePath());
string settingsBackupAndRestoreDir = GetSettingsBackupAndRestoreDir();
var results = BackupSettings(appBasePath, settingsBackupAndRestoreDir, true);

View File

@@ -22,7 +22,14 @@ namespace Microsoft.PowerToys.Settings.UI.Library
private readonly ISettingsPath _settingsPath;
private readonly JsonSerializerOptions _serializerOptions;
public SettingsUtils()
/// <summary>
/// Gets the default instance of the <see cref="SettingsUtils"/> class for general use.
/// Same as instantiating a new instance with the <see cref="SettingsUtils(IFileSystem?, JsonSerializerOptions?)"/> constructor with a new <see cref="FileSystem"/> object as the first argument and <c>null</c> as the second argument.
/// </summary>
/// <remarks>For using in tests, you should use one of the public constructors.</remarks>
public static SettingsUtils Default { get; } = new SettingsUtils();
private SettingsUtils()
: this(new FileSystem())
{
}
@@ -234,7 +241,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
public static (bool Success, string Message, string Severity, bool LastBackupExists, string OptionalMessage) BackupSettings()
{
var settingsBackupAndRestoreUtilsX = SettingsBackupAndRestoreUtils.Instance;
var settingsUtils = new SettingsUtils();
var settingsUtils = Default;
var appBasePath = Path.GetDirectoryName(settingsUtils._settingsPath.GetSettingsPath(string.Empty, string.Empty));
string settingsBackupAndRestoreDir = settingsBackupAndRestoreUtilsX.GetSettingsBackupAndRestoreDir();
@@ -247,7 +254,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
public static (bool Success, string Message, string Severity) RestoreSettings()
{
var settingsBackupAndRestoreUtilsX = SettingsBackupAndRestoreUtils.Instance;
var settingsUtils = new SettingsUtils();
var settingsUtils = Default;
var appBasePath = Path.GetDirectoryName(settingsUtils._settingsPath.GetSettingsPath(string.Empty, string.Empty));
string settingsBackupAndRestoreDir = settingsBackupAndRestoreUtilsX.GetSettingsBackupAndRestoreDir();
return settingsBackupAndRestoreUtilsX.RestoreSettings(appBasePath, settingsBackupAndRestoreDir);

View File

@@ -48,7 +48,7 @@ public sealed class GetSettingCommandLineCommand
var modulesSettings = new Dictionary<string, Dictionary<string, object>>();
var settingsAssembly = CommandLineUtils.GetSettingsAssembly();
var settingsUtils = new SettingsUtils();
var settingsUtils = SettingsUtils.Default;
var enabledModules = SettingsRepository<GeneralSettings>.GetInstance(settingsUtils).SettingsConfig.Enabled;

View File

@@ -67,7 +67,7 @@ namespace ViewModelTests
using (var viewModel = new ColorPickerViewModel(
ISettingsUtilsMocks.GetStubSettingsUtils<ColorPickerSettings>().Object,
SettingsRepository<GeneralSettings>.GetInstance(ISettingsUtilsMocks.GetStubSettingsUtils<GeneralSettings>().Object),
SettingsRepository<ColorPickerSettings>.GetInstance(new SettingsUtils()),
SettingsRepository<ColorPickerSettings>.GetInstance(SettingsUtils.Default),
ColorPickerIsEnabledByDefaultIPC))
{
Assert.IsTrue(viewModel.IsEnabled);

View File

@@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.
using System;
using System.IO.Abstractions;
using System.Text.Json;
using Microsoft.PowerToys.Settings.UI.Library;
@@ -120,7 +121,7 @@ namespace ViewModelTests
[TestMethod]
public void IsEnabledShouldDisableModuleWhenSuccessful()
{
Mock<SettingsUtils> mockSettingsUtils = new Mock<SettingsUtils>();
Mock<SettingsUtils> mockSettingsUtils = new Mock<SettingsUtils>(new FileSystem(), null);
Func<string, int> sendMockIPCConfigMSG = msg =>
{
@@ -140,7 +141,7 @@ namespace ViewModelTests
[TestMethod]
public void ShiftDragShouldSetValue2FalseWhenSuccessful()
{
Mock<SettingsUtils> mockSettingsUtils = new Mock<SettingsUtils>();
Mock<SettingsUtils> mockSettingsUtils = new Mock<SettingsUtils>(new FileSystem(), null);
// arrange
FancyZonesViewModel viewModel = new FancyZonesViewModel(mockSettingsUtils.Object, SettingsRepository<GeneralSettings>.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository<FancyZonesSettings>.GetInstance(mockFancyZonesSettingsUtils.Object), sendMockIPCConfigMSG, FancyZonesTestFolderName);
@@ -158,7 +159,7 @@ namespace ViewModelTests
[TestMethod]
public void OverrideSnapHotkeysShouldSetValue2TrueWhenSuccessful()
{
Mock<SettingsUtils> mockSettingsUtils = new Mock<SettingsUtils>();
Mock<SettingsUtils> mockSettingsUtils = new Mock<SettingsUtils>(new FileSystem(), null);
// arrange
FancyZonesViewModel viewModel = new FancyZonesViewModel(mockSettingsUtils.Object, SettingsRepository<GeneralSettings>.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository<FancyZonesSettings>.GetInstance(mockFancyZonesSettingsUtils.Object), sendMockIPCConfigMSG, FancyZonesTestFolderName);
@@ -176,7 +177,7 @@ namespace ViewModelTests
[TestMethod]
public void MoveWindowsAcrossMonitorsShouldSetValue2TrueWhenSuccessful()
{
Mock<SettingsUtils> mockSettingsUtils = new Mock<SettingsUtils>();
Mock<SettingsUtils> mockSettingsUtils = new Mock<SettingsUtils>(new FileSystem(), null);
// arrange
FancyZonesViewModel viewModel = new FancyZonesViewModel(mockSettingsUtils.Object, SettingsRepository<GeneralSettings>.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository<FancyZonesSettings>.GetInstance(mockFancyZonesSettingsUtils.Object), sendMockIPCConfigMSG, FancyZonesTestFolderName);
@@ -194,7 +195,7 @@ namespace ViewModelTests
[TestMethod]
public void MoveWindowsBasedOnPositionShouldSetValue2TrueWhenSuccessful()
{
Mock<SettingsUtils> mockSettingsUtils = new Mock<SettingsUtils>();
Mock<SettingsUtils> mockSettingsUtils = new Mock<SettingsUtils>(new FileSystem(), null);
// arrange
FancyZonesViewModel viewModel = new FancyZonesViewModel(mockSettingsUtils.Object, SettingsRepository<GeneralSettings>.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository<FancyZonesSettings>.GetInstance(mockFancyZonesSettingsUtils.Object), sendMockIPCConfigMSG, FancyZonesTestFolderName);
@@ -217,7 +218,7 @@ namespace ViewModelTests
[TestMethod]
public void QuickLayoutSwitchShouldSetValue2FalseWhenSuccessful()
{
Mock<SettingsUtils> mockSettingsUtils = new Mock<SettingsUtils>();
Mock<SettingsUtils> mockSettingsUtils = new Mock<SettingsUtils>(new FileSystem(), null);
// arrange
FancyZonesViewModel viewModel = new FancyZonesViewModel(mockSettingsUtils.Object, SettingsRepository<GeneralSettings>.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository<FancyZonesSettings>.GetInstance(mockFancyZonesSettingsUtils.Object), sendMockIPCConfigMSG, FancyZonesTestFolderName);
@@ -235,7 +236,7 @@ namespace ViewModelTests
[TestMethod]
public void FlashZonesOnQuickSwitchShouldSetValue2FalseWhenSuccessful()
{
Mock<SettingsUtils> mockSettingsUtils = new Mock<SettingsUtils>();
Mock<SettingsUtils> mockSettingsUtils = new Mock<SettingsUtils>(new FileSystem(), null);
// arrange
FancyZonesViewModel viewModel = new FancyZonesViewModel(mockSettingsUtils.Object, SettingsRepository<GeneralSettings>.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository<FancyZonesSettings>.GetInstance(mockFancyZonesSettingsUtils.Object), sendMockIPCConfigMSG, FancyZonesTestFolderName);
@@ -253,7 +254,7 @@ namespace ViewModelTests
[TestMethod]
public void MakeDraggedWindowsTransparentShouldSetValue2TrueWhenSuccessful()
{
Mock<SettingsUtils> mockSettingsUtils = new Mock<SettingsUtils>();
Mock<SettingsUtils> mockSettingsUtils = new Mock<SettingsUtils>(new FileSystem(), null);
// arrange
FancyZonesViewModel viewModel = new FancyZonesViewModel(mockSettingsUtils.Object, SettingsRepository<GeneralSettings>.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository<FancyZonesSettings>.GetInstance(mockFancyZonesSettingsUtils.Object), sendMockIPCConfigMSG, FancyZonesTestFolderName);
@@ -271,7 +272,7 @@ namespace ViewModelTests
[TestMethod]
public void MouseSwitchShouldSetValue2TrueWhenSuccessful()
{
Mock<SettingsUtils> mockSettingsUtils = new Mock<SettingsUtils>();
Mock<SettingsUtils> mockSettingsUtils = new Mock<SettingsUtils>(new FileSystem(), null);
// arrange
FancyZonesViewModel viewModel = new FancyZonesViewModel(mockSettingsUtils.Object, SettingsRepository<GeneralSettings>.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository<FancyZonesSettings>.GetInstance(mockFancyZonesSettingsUtils.Object), sendMockIPCConfigMSG, FancyZonesTestFolderName);
@@ -289,7 +290,7 @@ namespace ViewModelTests
[TestMethod]
public void DisplayOrWorkAreaChangeMoveWindowsShouldSetValue2FalseWhenSuccessful()
{
Mock<SettingsUtils> mockSettingsUtils = new Mock<SettingsUtils>();
Mock<SettingsUtils> mockSettingsUtils = new Mock<SettingsUtils>(new FileSystem(), null);
// arrange
FancyZonesViewModel viewModel = new FancyZonesViewModel(mockSettingsUtils.Object, SettingsRepository<GeneralSettings>.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository<FancyZonesSettings>.GetInstance(mockFancyZonesSettingsUtils.Object), sendMockIPCConfigMSG, FancyZonesTestFolderName);
@@ -307,7 +308,7 @@ namespace ViewModelTests
[TestMethod]
public void ZoneSetChangeMoveWindowsShouldSetValue2TrueWhenSuccessful()
{
Mock<SettingsUtils> mockSettingsUtils = new Mock<SettingsUtils>();
Mock<SettingsUtils> mockSettingsUtils = new Mock<SettingsUtils>(new FileSystem(), null);
// arrange
FancyZonesViewModel viewModel = new FancyZonesViewModel(mockSettingsUtils.Object, SettingsRepository<GeneralSettings>.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository<FancyZonesSettings>.GetInstance(mockFancyZonesSettingsUtils.Object), sendMockIPCConfigMSG, FancyZonesTestFolderName);
@@ -325,7 +326,7 @@ namespace ViewModelTests
[TestMethod]
public void AppLastZoneMoveWindowsShouldSetValue2TrueWhenSuccessful()
{
Mock<SettingsUtils> mockSettingsUtils = new Mock<SettingsUtils>();
Mock<SettingsUtils> mockSettingsUtils = new Mock<SettingsUtils>(new FileSystem(), null);
// arrange
FancyZonesViewModel viewModel = new FancyZonesViewModel(mockSettingsUtils.Object, SettingsRepository<GeneralSettings>.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository<FancyZonesSettings>.GetInstance(mockFancyZonesSettingsUtils.Object), sendMockIPCConfigMSG, FancyZonesTestFolderName);
@@ -343,7 +344,7 @@ namespace ViewModelTests
[TestMethod]
public void OpenWindowOnActiveMonitorShouldSetValue2TrueWhenSuccessful()
{
Mock<SettingsUtils> mockSettingsUtils = new Mock<SettingsUtils>();
Mock<SettingsUtils> mockSettingsUtils = new Mock<SettingsUtils>(new FileSystem(), null);
// arrange
FancyZonesViewModel viewModel = new FancyZonesViewModel(mockSettingsUtils.Object, SettingsRepository<GeneralSettings>.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository<FancyZonesSettings>.GetInstance(mockFancyZonesSettingsUtils.Object), sendMockIPCConfigMSG, FancyZonesTestFolderName);
@@ -361,7 +362,7 @@ namespace ViewModelTests
[TestMethod]
public void RestoreSizeShouldSetValue2TrueWhenSuccessful()
{
Mock<SettingsUtils> mockSettingsUtils = new Mock<SettingsUtils>();
Mock<SettingsUtils> mockSettingsUtils = new Mock<SettingsUtils>(new FileSystem(), null);
// arrange
FancyZonesViewModel viewModel = new FancyZonesViewModel(mockSettingsUtils.Object, SettingsRepository<GeneralSettings>.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository<FancyZonesSettings>.GetInstance(mockFancyZonesSettingsUtils.Object), sendMockIPCConfigMSG, FancyZonesTestFolderName);
@@ -379,7 +380,7 @@ namespace ViewModelTests
[TestMethod]
public void UseCursorPosEditorStartupScreenShouldSetValue2FalseWhenSuccessful()
{
Mock<SettingsUtils> mockSettingsUtils = new Mock<SettingsUtils>();
Mock<SettingsUtils> mockSettingsUtils = new Mock<SettingsUtils>(new FileSystem(), null);
// arrange
FancyZonesViewModel viewModel = new FancyZonesViewModel(mockSettingsUtils.Object, SettingsRepository<GeneralSettings>.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository<FancyZonesSettings>.GetInstance(mockFancyZonesSettingsUtils.Object), sendMockIPCConfigMSG, FancyZonesTestFolderName);
@@ -397,7 +398,7 @@ namespace ViewModelTests
[TestMethod]
public void ShowOnAllMonitorsShouldSetValue2TrueWhenSuccessful()
{
Mock<SettingsUtils> mockSettingsUtils = new Mock<SettingsUtils>();
Mock<SettingsUtils> mockSettingsUtils = new Mock<SettingsUtils>(new FileSystem(), null);
// arrange
FancyZonesViewModel viewModel = new FancyZonesViewModel(mockSettingsUtils.Object, SettingsRepository<GeneralSettings>.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository<FancyZonesSettings>.GetInstance(mockFancyZonesSettingsUtils.Object), sendMockIPCConfigMSG, FancyZonesTestFolderName);
@@ -415,7 +416,7 @@ namespace ViewModelTests
[TestMethod]
public void SpanZonesAcrossMonitorsShouldSetValue2TrueWhenSuccessful()
{
Mock<SettingsUtils> mockSettingsUtils = new Mock<SettingsUtils>();
Mock<SettingsUtils> mockSettingsUtils = new Mock<SettingsUtils>(new FileSystem(), null);
// arrange
FancyZonesViewModel viewModel = new FancyZonesViewModel(mockSettingsUtils.Object, SettingsRepository<GeneralSettings>.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository<FancyZonesSettings>.GetInstance(mockFancyZonesSettingsUtils.Object), sendMockIPCConfigMSG, FancyZonesTestFolderName);
@@ -433,7 +434,7 @@ namespace ViewModelTests
[TestMethod]
public void OverlappingZonesAlgorithmIndexShouldSetValue2AnotherWhenSuccessful()
{
Mock<SettingsUtils> mockSettingsUtils = new Mock<SettingsUtils>();
Mock<SettingsUtils> mockSettingsUtils = new Mock<SettingsUtils>(new FileSystem(), null);
// arrange
FancyZonesViewModel viewModel = new FancyZonesViewModel(mockSettingsUtils.Object, SettingsRepository<GeneralSettings>.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository<FancyZonesSettings>.GetInstance(mockFancyZonesSettingsUtils.Object), sendMockIPCConfigMSG, FancyZonesTestFolderName);
@@ -451,7 +452,7 @@ namespace ViewModelTests
[TestMethod]
public void AllowChildWindowsToSnapShouldSetValue2TrueWhenSuccessful()
{
Mock<SettingsUtils> mockSettingsUtils = new Mock<SettingsUtils>();
Mock<SettingsUtils> mockSettingsUtils = new Mock<SettingsUtils>(new FileSystem(), null);
// arrange
FancyZonesViewModel viewModel = new FancyZonesViewModel(mockSettingsUtils.Object, SettingsRepository<GeneralSettings>.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository<FancyZonesSettings>.GetInstance(mockFancyZonesSettingsUtils.Object), sendMockIPCConfigMSG, FancyZonesTestFolderName);
@@ -469,7 +470,7 @@ namespace ViewModelTests
[TestMethod]
public void DisableRoundCornersOnSnapShouldSetValue2TrueWhenSuccessful()
{
Mock<SettingsUtils> mockSettingsUtils = new Mock<SettingsUtils>();
Mock<SettingsUtils> mockSettingsUtils = new Mock<SettingsUtils>(new FileSystem(), null);
// arrange
FancyZonesViewModel viewModel = new FancyZonesViewModel(mockSettingsUtils.Object, SettingsRepository<GeneralSettings>.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository<FancyZonesSettings>.GetInstance(mockFancyZonesSettingsUtils.Object), sendMockIPCConfigMSG, FancyZonesTestFolderName);
@@ -487,7 +488,7 @@ namespace ViewModelTests
[TestMethod]
public void ZoneHighlightColorShouldSetColorValue2WhiteWhenSuccessful()
{
Mock<SettingsUtils> mockSettingsUtils = new Mock<SettingsUtils>();
Mock<SettingsUtils> mockSettingsUtils = new Mock<SettingsUtils>(new FileSystem(), null);
// arrange
FancyZonesViewModel viewModel = new FancyZonesViewModel(mockSettingsUtils.Object, SettingsRepository<GeneralSettings>.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository<FancyZonesSettings>.GetInstance(mockFancyZonesSettingsUtils.Object), sendMockIPCConfigMSG, FancyZonesTestFolderName);
@@ -505,7 +506,7 @@ namespace ViewModelTests
[TestMethod]
public void ZoneBorderColorShouldSetColorValue2WhiteWhenSuccessful()
{
Mock<SettingsUtils> mockSettingsUtils = new Mock<SettingsUtils>();
Mock<SettingsUtils> mockSettingsUtils = new Mock<SettingsUtils>(new FileSystem(), null);
// arrange
FancyZonesViewModel viewModel = new FancyZonesViewModel(mockSettingsUtils.Object, SettingsRepository<GeneralSettings>.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository<FancyZonesSettings>.GetInstance(mockFancyZonesSettingsUtils.Object), sendMockIPCConfigMSG, FancyZonesTestFolderName);
@@ -523,7 +524,7 @@ namespace ViewModelTests
[TestMethod]
public void ZoneInActiveColorShouldSetColorValue2WhiteWhenSuccessful()
{
Mock<SettingsUtils> mockSettingsUtils = new Mock<SettingsUtils>();
Mock<SettingsUtils> mockSettingsUtils = new Mock<SettingsUtils>(new FileSystem(), null);
// arrange
FancyZonesViewModel viewModel = new FancyZonesViewModel(mockSettingsUtils.Object, SettingsRepository<GeneralSettings>.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository<FancyZonesSettings>.GetInstance(mockFancyZonesSettingsUtils.Object), sendMockIPCConfigMSG, FancyZonesTestFolderName);
@@ -541,7 +542,7 @@ namespace ViewModelTests
[TestMethod]
public void ExcludedAppsShouldSetColorValue2WhiteWhenSuccessful()
{
Mock<SettingsUtils> mockSettingsUtils = new Mock<SettingsUtils>();
Mock<SettingsUtils> mockSettingsUtils = new Mock<SettingsUtils>(new FileSystem(), null);
// arrange
FancyZonesViewModel viewModel = new FancyZonesViewModel(mockSettingsUtils.Object, SettingsRepository<GeneralSettings>.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository<FancyZonesSettings>.GetInstance(mockFancyZonesSettingsUtils.Object), sendMockIPCConfigMSG, FancyZonesTestFolderName);
@@ -559,7 +560,7 @@ namespace ViewModelTests
[TestMethod]
public void HighlightOpacityShouldSetOpacityValueTo60WhenSuccessful()
{
Mock<SettingsUtils> mockSettingsUtils = new Mock<SettingsUtils>();
Mock<SettingsUtils> mockSettingsUtils = new Mock<SettingsUtils>(new FileSystem(), null);
// arrange
FancyZonesViewModel viewModel = new FancyZonesViewModel(mockSettingsUtils.Object, SettingsRepository<GeneralSettings>.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository<FancyZonesSettings>.GetInstance(mockFancyZonesSettingsUtils.Object), sendMockIPCConfigMSG, FancyZonesTestFolderName);

View File

@@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.
using System;
using System.IO.Abstractions;
using System.Text.Json;
using Microsoft.PowerToys.Settings.UI.Library;
@@ -69,7 +70,7 @@ namespace ViewModelTests
[TestMethod]
public void IsEnabledShouldEnableModuleWhenSuccessful()
{
var settingsUtilsMock = new Mock<SettingsUtils>();
var settingsUtilsMock = new Mock<SettingsUtils>(new FileSystem(), null);
// Assert
// Initialize mock function of sending IPC message.

View File

@@ -22,7 +22,7 @@ namespace Microsoft.PowerToys.Settings.UI.Helpers
static HotkeyConflictIgnoreHelper()
{
_settingsUtils = new SettingsUtils();
_settingsUtils = SettingsUtils.Default;
_generalSettingsRepository = SettingsRepository<GeneralSettings>.GetInstance(_settingsUtils);
}

View File

@@ -133,7 +133,7 @@ namespace Microsoft.PowerToys.Settings.UI
var settingValue = cmdArgs[3];
try
{
SetSettingCommandLineCommand.Execute(settingName, settingValue, new SettingsUtils());
SetSettingCommandLineCommand.Execute(settingName, settingValue, SettingsUtils.Default);
}
catch (Exception ex)
{
@@ -151,7 +151,7 @@ namespace Microsoft.PowerToys.Settings.UI
{
using (var settings = JsonDocument.Parse(File.ReadAllText(ipcFileName)))
{
SetAdditionalSettingsCommandLineCommand.Execute(moduleName, settings, new SettingsUtils());
SetAdditionalSettingsCommandLineCommand.Execute(moduleName, settings, SettingsUtils.Default);
}
}
catch (Exception ex)
@@ -357,7 +357,7 @@ namespace Microsoft.PowerToys.Settings.UI
return 0;
}
private static ISettingsUtils settingsUtils = new SettingsUtils();
private static ISettingsUtils settingsUtils = SettingsUtils.Default;
private static ThemeService themeService = new ThemeService(SettingsRepository<GeneralSettings>.GetInstance(settingsUtils));
public static ThemeService ThemeService => themeService;

View File

@@ -26,7 +26,7 @@ namespace Microsoft.PowerToys.Settings.UI.SettingsXAML.Controls.Dashboard
public ShortcutConflictWindow()
{
var settingsUtils = new SettingsUtils();
var settingsUtils = SettingsUtils.Default;
ViewModel = new ShortcutConflictViewModel(
settingsUtils,
SettingsRepository<GeneralSettings>.GetInstance(settingsUtils),

View File

@@ -24,7 +24,7 @@ namespace Microsoft.PowerToys.Settings.UI.Flyout
{
this.InitializeComponent();
var settingsUtils = new SettingsUtils();
var settingsUtils = SettingsUtils.Default;
ViewModel = new AllAppsViewModel(SettingsRepository<GeneralSettings>.GetInstance(settingsUtils), Views.ShellPage.SendDefaultIPCMessage);
DataContext = ViewModel;
}

View File

@@ -27,7 +27,7 @@ namespace Microsoft.PowerToys.Settings.UI.Flyout
public LaunchPage()
{
this.InitializeComponent();
var settingsUtils = new SettingsUtils();
var settingsUtils = SettingsUtils.Default;
ViewModel = new LauncherViewModel(SettingsRepository<GeneralSettings>.GetInstance(settingsUtils), Views.ShellPage.SendDefaultIPCMessage);
DataContext = ViewModel;
}
@@ -51,7 +51,7 @@ namespace Microsoft.PowerToys.Settings.UI.Flyout
break;
case ModuleType.EnvironmentVariables: // Launch Environment Variables
{
bool launchAdmin = SettingsRepository<EnvironmentVariablesSettings>.GetInstance(new SettingsUtils()).SettingsConfig.Properties.LaunchAdministrator;
bool launchAdmin = SettingsRepository<EnvironmentVariablesSettings>.GetInstance(SettingsUtils.Default).SettingsConfig.Properties.LaunchAdministrator;
string eventName = !App.IsElevated && launchAdmin
? Constants.ShowEnvironmentVariablesAdminSharedEvent()
: Constants.ShowEnvironmentVariablesSharedEvent();
@@ -74,7 +74,7 @@ namespace Microsoft.PowerToys.Settings.UI.Flyout
case ModuleType.Hosts: // Launch Hosts
{
bool launchAdmin = SettingsRepository<HostsSettings>.GetInstance(new SettingsUtils()).SettingsConfig.Properties.LaunchAdministrator;
bool launchAdmin = SettingsRepository<HostsSettings>.GetInstance(SettingsUtils.Default).SettingsConfig.Properties.LaunchAdministrator;
string eventName = !App.IsElevated && launchAdmin
? Constants.ShowHostsAdminSharedEvent()
: Constants.ShowHostsSharedEvent();

View File

@@ -81,7 +81,7 @@ namespace Microsoft.PowerToys.Settings.UI
// open main window
ShellPage.SetUpdatingGeneralSettingsCallback((ModuleType moduleType, bool isEnabled) =>
{
SettingsRepository<GeneralSettings> repository = SettingsRepository<GeneralSettings>.GetInstance(new SettingsUtils());
SettingsRepository<GeneralSettings> repository = SettingsRepository<GeneralSettings>.GetInstance(SettingsUtils.Default);
GeneralSettings generalSettingsConfig = repository.SettingsConfig;
bool needToUpdate = ModuleHelper.GetIsModuleEnabled(generalSettingsConfig, moduleType) != isEnabled;

View File

@@ -35,9 +35,9 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
protected override void OnNavigatedTo(NavigationEventArgs e)
{
ViewModel.LogOpeningModuleEvent();
AdvancedPasteUIHotkeyControl.Keys = SettingsRepository<AdvancedPasteSettings>.GetInstance(new SettingsUtils()).SettingsConfig.Properties.AdvancedPasteUIShortcut.GetKeysList();
PasteAsPlainTextHotkeyControl.Keys = SettingsRepository<AdvancedPasteSettings>.GetInstance(new SettingsUtils()).SettingsConfig.Properties.PasteAsPlainTextShortcut.GetKeysList();
PasteAsMarkdownHotkeyControl.Keys = SettingsRepository<AdvancedPasteSettings>.GetInstance(new SettingsUtils()).SettingsConfig.Properties.PasteAsMarkdownShortcut.GetKeysList();
AdvancedPasteUIHotkeyControl.Keys = SettingsRepository<AdvancedPasteSettings>.GetInstance(SettingsUtils.Default).SettingsConfig.Properties.AdvancedPasteUIShortcut.GetKeysList();
PasteAsPlainTextHotkeyControl.Keys = SettingsRepository<AdvancedPasteSettings>.GetInstance(SettingsUtils.Default).SettingsConfig.Properties.PasteAsPlainTextShortcut.GetKeysList();
PasteAsMarkdownHotkeyControl.Keys = SettingsRepository<AdvancedPasteSettings>.GetInstance(SettingsUtils.Default).SettingsConfig.Properties.PasteAsMarkdownShortcut.GetKeysList();
// TODO(stefan): Check how to remove additional space if item is set to Collapsed.
if (PasteAsMarkdownHotkeyControl.Keys.Count > 0)
@@ -45,7 +45,7 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
PasteAsMarkdownHotkeyControl.Visibility = Microsoft.UI.Xaml.Visibility.Visible;
}
PasteAsJsonHotkeyControl.Keys = SettingsRepository<AdvancedPasteSettings>.GetInstance(new SettingsUtils()).SettingsConfig.Properties.PasteAsJsonShortcut.GetKeysList();
PasteAsJsonHotkeyControl.Keys = SettingsRepository<AdvancedPasteSettings>.GetInstance(SettingsUtils.Default).SettingsConfig.Properties.PasteAsJsonShortcut.GetKeysList();
if (PasteAsJsonHotkeyControl.Keys.Count > 0)
{
PasteAsJsonHotkeyControl.Visibility = Microsoft.UI.Xaml.Visibility.Visible;

View File

@@ -35,7 +35,7 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
protected override void OnNavigatedTo(NavigationEventArgs e)
{
ViewModel.LogOpeningModuleEvent();
HotkeyControl.Keys = SettingsRepository<AlwaysOnTopSettings>.GetInstance(new SettingsUtils()).SettingsConfig.Properties.Hotkey.Value.GetKeysList();
HotkeyControl.Keys = SettingsRepository<AlwaysOnTopSettings>.GetInstance(SettingsUtils.Default).SettingsConfig.Properties.Hotkey.Value.GetKeysList();
}
protected override void OnNavigatedFrom(NavigationEventArgs e)

View File

@@ -50,7 +50,7 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
protected override void OnNavigatedTo(NavigationEventArgs e)
{
ViewModel.LogOpeningModuleEvent();
ColorPickerSettings settings = new SettingsUtils().GetSettingsOrDefault<ColorPickerSettings, ColorPickerSettingsVersion1>(ColorPickerSettings.ModuleName, settingsUpgrader: ColorPickerSettings.UpgradeSettings);
ColorPickerSettings settings = SettingsUtils.Default.GetSettingsOrDefault<ColorPickerSettings, ColorPickerSettingsVersion1>(ColorPickerSettings.ModuleName, settingsUpgrader: ColorPickerSettings.UpgradeSettings);
HotkeyControl.Keys = settings.Properties.ActivationShortcut.GetKeysList();
}

View File

@@ -35,8 +35,8 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
protected override void OnNavigatedTo(NavigationEventArgs e)
{
ViewModel.LogOpeningModuleEvent();
ReparentHotkeyControl.Keys = SettingsRepository<CropAndLockSettings>.GetInstance(new SettingsUtils()).SettingsConfig.Properties.ReparentHotkey.Value.GetKeysList();
ThumbnailHotkeyControl.Keys = SettingsRepository<CropAndLockSettings>.GetInstance(new SettingsUtils()).SettingsConfig.Properties.ThumbnailHotkey.Value.GetKeysList();
ReparentHotkeyControl.Keys = SettingsRepository<CropAndLockSettings>.GetInstance(SettingsUtils.Default).SettingsConfig.Properties.ReparentHotkey.Value.GetKeysList();
ThumbnailHotkeyControl.Keys = SettingsRepository<CropAndLockSettings>.GetInstance(SettingsUtils.Default).SettingsConfig.Properties.ThumbnailHotkey.Value.GetKeysList();
}
protected override void OnNavigatedFrom(NavigationEventArgs e)

View File

@@ -37,7 +37,7 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
private void Launch_EnvironmentVariables_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
{
bool launchAdmin = SettingsRepository<EnvironmentVariablesSettings>.GetInstance(new SettingsUtils()).SettingsConfig.Properties.LaunchAdministrator;
bool launchAdmin = SettingsRepository<EnvironmentVariablesSettings>.GetInstance(SettingsUtils.Default).SettingsConfig.Properties.LaunchAdministrator;
string eventName = !App.IsElevated && launchAdmin
? Constants.ShowEnvironmentVariablesAdminSharedEvent()
: Constants.ShowEnvironmentVariablesSharedEvent();

View File

@@ -35,7 +35,7 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
protected override void OnNavigatedTo(NavigationEventArgs e)
{
ViewModel.LogOpeningModuleEvent();
HotkeyControl.Keys = SettingsRepository<FancyZonesSettings>.GetInstance(new SettingsUtils()).SettingsConfig.Properties.FancyzonesEditorHotkey.Value.GetKeysList();
HotkeyControl.Keys = SettingsRepository<FancyZonesSettings>.GetInstance(SettingsUtils.Default).SettingsConfig.Properties.FancyzonesEditorHotkey.Value.GetKeysList();
}
protected override void OnNavigatedFrom(NavigationEventArgs e)

View File

@@ -37,7 +37,7 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
private void Launch_Hosts_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
{
bool launchAdmin = SettingsRepository<HostsSettings>.GetInstance(new SettingsUtils()).SettingsConfig.Properties.LaunchAdministrator;
bool launchAdmin = SettingsRepository<HostsSettings>.GetInstance(SettingsUtils.Default).SettingsConfig.Properties.LaunchAdministrator;
string eventName = !App.IsElevated && launchAdmin
? Constants.ShowHostsAdminSharedEvent()
: Constants.ShowHostsSharedEvent();

View File

@@ -38,7 +38,7 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
protected override void OnNavigatedTo(NavigationEventArgs e)
{
ViewModel.LogOpeningModuleEvent();
HotkeyActivation.Keys = SettingsRepository<MeasureToolSettings>.GetInstance(new SettingsUtils()).SettingsConfig.Properties.ActivationShortcut.GetKeysList();
HotkeyActivation.Keys = SettingsRepository<MeasureToolSettings>.GetInstance(SettingsUtils.Default).SettingsConfig.Properties.ActivationShortcut.GetKeysList();
}
protected override void OnNavigatedFrom(NavigationEventArgs e)

View File

@@ -21,10 +21,10 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
ViewModel = new OobePowerToysModule(OobeShellPage.OobeShellHandler.Modules[(int)PowerToysModules.Overview]);
DataContext = ViewModel;
FancyZonesHotkeyControl.Keys = SettingsRepository<FancyZonesSettings>.GetInstance(new SettingsUtils()).SettingsConfig.Properties.FancyzonesEditorHotkey.Value.GetKeysList();
RunHotkeyControl.Keys = SettingsRepository<PowerLauncherSettings>.GetInstance(new SettingsUtils()).SettingsConfig.Properties.OpenPowerLauncher.GetKeysList();
ColorPickerHotkeyControl.Keys = SettingsRepository<ColorPickerSettings>.GetInstance(new SettingsUtils()).SettingsConfig.Properties.ActivationShortcut.GetKeysList();
AlwaysOnTopHotkeyControl.Keys = SettingsRepository<AlwaysOnTopSettings>.GetInstance(new SettingsUtils()).SettingsConfig.Properties.Hotkey.Value.GetKeysList();
FancyZonesHotkeyControl.Keys = SettingsRepository<FancyZonesSettings>.GetInstance(SettingsUtils.Default).SettingsConfig.Properties.FancyzonesEditorHotkey.Value.GetKeysList();
RunHotkeyControl.Keys = SettingsRepository<PowerLauncherSettings>.GetInstance(SettingsUtils.Default).SettingsConfig.Properties.OpenPowerLauncher.GetKeysList();
ColorPickerHotkeyControl.Keys = SettingsRepository<ColorPickerSettings>.GetInstance(SettingsUtils.Default).SettingsConfig.Properties.ActivationShortcut.GetKeysList();
AlwaysOnTopHotkeyControl.Keys = SettingsRepository<AlwaysOnTopSettings>.GetInstance(SettingsUtils.Default).SettingsConfig.Properties.Hotkey.Value.GetKeysList();
}
private void SettingsLaunchButton_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)

View File

@@ -38,7 +38,7 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
protected override void OnNavigatedTo(NavigationEventArgs e)
{
ViewModel.LogOpeningModuleEvent();
HotkeyControl.Keys = SettingsRepository<PeekSettings>.GetInstance(new SettingsUtils()).SettingsConfig.Properties.ActivationShortcut.GetKeysList();
HotkeyControl.Keys = SettingsRepository<PeekSettings>.GetInstance(SettingsUtils.Default).SettingsConfig.Properties.ActivationShortcut.GetKeysList();
}
protected override void OnNavigatedFrom(NavigationEventArgs e)

View File

@@ -35,7 +35,7 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
protected override void OnNavigatedTo(NavigationEventArgs e)
{
ViewModel.LogOpeningModuleEvent();
HotkeyControl.Keys = SettingsRepository<PowerOcrSettings>.GetInstance(new SettingsUtils()).SettingsConfig.Properties.ActivationShortcut.GetKeysList();
HotkeyControl.Keys = SettingsRepository<PowerOcrSettings>.GetInstance(SettingsUtils.Default).SettingsConfig.Properties.ActivationShortcut.GetKeysList();
}
protected override void OnNavigatedFrom(NavigationEventArgs e)

View File

@@ -54,7 +54,7 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
{
ViewModel.LogOpeningModuleEvent();
HotkeyControl.Keys = SettingsRepository<PowerLauncherSettings>.GetInstance(new SettingsUtils()).SettingsConfig.Properties.OpenPowerLauncher.GetKeysList();
HotkeyControl.Keys = SettingsRepository<PowerLauncherSettings>.GetInstance(SettingsUtils.Default).SettingsConfig.Properties.OpenPowerLauncher.GetKeysList();
}
protected override void OnNavigatedFrom(NavigationEventArgs e)

View File

@@ -51,7 +51,7 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
public ObservableCollection<OobePowerToysModule> Modules { get; }
private static ISettingsUtils settingsUtils = new SettingsUtils();
private static ISettingsUtils settingsUtils = SettingsUtils.Default;
/* NOTE: Experimentation for OOBE is currently turned off on server side. Keeping this code in a comment to allow future experiments.
private bool ExperimentationToggleSwitchEnabled { get; set; } = true;

View File

@@ -56,7 +56,7 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
protected override void OnNavigatedTo(NavigationEventArgs e)
{
ViewModel.LogOpeningModuleEvent();
var settingsProperties = SettingsRepository<ShortcutGuideSettings>.GetInstance(new SettingsUtils()).SettingsConfig.Properties;
var settingsProperties = SettingsRepository<ShortcutGuideSettings>.GetInstance(SettingsUtils.Default).SettingsConfig.Properties;
if ((bool)settingsProperties.UseLegacyPressWinKeyBehavior.Value)
{

View File

@@ -38,7 +38,7 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
protected override void OnNavigatedTo(NavigationEventArgs e)
{
ViewModel.LogOpeningModuleEvent();
HotkeyControl.Keys = SettingsRepository<WorkspacesSettings>.GetInstance(new SettingsUtils()).SettingsConfig.Properties.Hotkey.Value.GetKeysList();
HotkeyControl.Keys = SettingsRepository<WorkspacesSettings>.GetInstance(SettingsUtils.Default).SettingsConfig.Properties.Hotkey.Value.GetKeysList();
}
protected override void OnNavigatedFrom(NavigationEventArgs e)

View File

@@ -44,7 +44,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views
public AdvancedPastePage()
{
var settingsUtils = new SettingsUtils();
var settingsUtils = SettingsUtils.Default;
ViewModel = new AdvancedPasteViewModel(
settingsUtils,
SettingsRepository<GeneralSettings>.GetInstance(settingsUtils),

View File

@@ -15,7 +15,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views
public AlwaysOnTopPage()
{
var settingsUtils = new SettingsUtils();
var settingsUtils = SettingsUtils.Default;
ViewModel = new AlwaysOnTopViewModel(settingsUtils, SettingsRepository<GeneralSettings>.GetInstance(settingsUtils), SettingsRepository<AlwaysOnTopSettings>.GetInstance(settingsUtils), ShellPage.SendDefaultIPCMessage);
DataContext = ViewModel;
InitializeComponent();

View File

@@ -36,7 +36,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views
{
_dispatcherQueue = DispatcherQueue.GetForCurrentThread();
_fileSystem = new FileSystem();
_settingsUtils = new SettingsUtils();
_settingsUtils = SettingsUtils.Default;
_sendConfigMsg = ShellPage.SendDefaultIPCMessage;
ViewModel = new AwakeViewModel();

View File

@@ -19,7 +19,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views
public CmdPalPage()
{
var settingsUtils = new SettingsUtils();
var settingsUtils = SettingsUtils.Default;
ViewModel = new CmdPalViewModel(
settingsUtils,
SettingsRepository<GeneralSettings>.GetInstance(settingsUtils),

View File

@@ -27,7 +27,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views
public ColorPickerPage()
{
var settingsUtils = new SettingsUtils();
var settingsUtils = SettingsUtils.Default;
ViewModel = new ColorPickerViewModel(
settingsUtils,
SettingsRepository<GeneralSettings>.GetInstance(settingsUtils),

View File

@@ -15,7 +15,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views
public CropAndLockPage()
{
var settingsUtils = new SettingsUtils();
var settingsUtils = SettingsUtils.Default;
ViewModel = new CropAndLockViewModel(settingsUtils, SettingsRepository<GeneralSettings>.GetInstance(settingsUtils), SettingsRepository<CropAndLockSettings>.GetInstance(settingsUtils), ShellPage.SendDefaultIPCMessage);
DataContext = ViewModel;
InitializeComponent();

View File

@@ -34,7 +34,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views
public DashboardPage()
{
InitializeComponent();
var settingsUtils = new SettingsUtils();
var settingsUtils = SettingsUtils.Default;
ViewModel = new DashboardViewModel(
SettingsRepository<GeneralSettings>.GetInstance(settingsUtils), ShellPage.SendDefaultIPCMessage);

View File

@@ -16,7 +16,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views
public EnvironmentVariablesPage()
{
InitializeComponent();
var settingsUtils = new SettingsUtils();
var settingsUtils = SettingsUtils.Default;
ViewModel = new EnvironmentVariablesViewModel(settingsUtils, SettingsRepository<GeneralSettings>.GetInstance(settingsUtils), SettingsRepository<EnvironmentVariablesSettings>.GetInstance(settingsUtils), ShellPage.SendDefaultIPCMessage, App.IsElevated);
}

View File

@@ -16,7 +16,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views
public FancyZonesPage()
{
InitializeComponent();
var settingsUtils = new SettingsUtils();
var settingsUtils = SettingsUtils.Default;
ViewModel = new FancyZonesViewModel(settingsUtils, SettingsRepository<GeneralSettings>.GetInstance(settingsUtils), SettingsRepository<FancyZonesSettings>.GetInstance(settingsUtils), ShellPage.SendDefaultIPCMessage);
DataContext = ViewModel;
Loaded += (s, e) => ViewModel.OnPageLoaded();

View File

@@ -15,7 +15,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views
public FileLocksmithPage()
{
var settingsUtils = new SettingsUtils();
var settingsUtils = SettingsUtils.Default;
ViewModel = new FileLocksmithViewModel(settingsUtils, SettingsRepository<GeneralSettings>.GetInstance(settingsUtils), ShellPage.SendDefaultIPCMessage);
DataContext = ViewModel;
InitializeComponent();

View File

@@ -38,7 +38,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views
// Load string resources
var loader = Helpers.ResourceLoaderInstance.ResourceLoader;
var settingsUtils = new SettingsUtils();
var settingsUtils = SettingsUtils.Default;
Action stateUpdatingAction = () =>
{

View File

@@ -16,7 +16,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views
public HostsPage()
{
InitializeComponent();
var settingsUtils = new SettingsUtils();
var settingsUtils = SettingsUtils.Default;
ViewModel = new HostsViewModel(settingsUtils, SettingsRepository<GeneralSettings>.GetInstance(settingsUtils), SettingsRepository<HostsSettings>.GetInstance(settingsUtils), ShellPage.SendDefaultIPCMessage, App.IsElevated);
BackupsCountInputSettingsCard.Header = ResourceLoaderInstance.ResourceLoader.GetString("Hosts_Backup_CountInput_Header");
BackupsCountInputSettingsCard.Description = ResourceLoaderInstance.ResourceLoader.GetString("Hosts_Backup_CountInput_Description");

View File

@@ -21,7 +21,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views
public ImageResizerPage()
{
InitializeComponent();
var settingsUtils = new SettingsUtils();
var settingsUtils = SettingsUtils.Default;
var resourceLoader = ResourceLoaderInstance.ResourceLoader;
Func<string, string> loader = resourceLoader.GetString;

View File

@@ -28,7 +28,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views
public KeyboardManagerPage()
{
var settingsUtils = new SettingsUtils();
var settingsUtils = SettingsUtils.Default;
ViewModel = new KeyboardManagerViewModel(settingsUtils, SettingsRepository<GeneralSettings>.GetInstance(settingsUtils), ShellPage.SendDefaultIPCMessage, FilterRemapKeysList);
watcher = Helper.GetFileWatcher(

View File

@@ -40,7 +40,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views
public LightSwitchPage()
{
this.settingsUtils = new SettingsUtils();
this.settingsUtils = SettingsUtils.Default;
this.sendConfigMsg = ShellPage.SendDefaultIPCMessage;
this.generalSettingsRepository = SettingsRepository<GeneralSettings>.GetInstance(this.settingsUtils);

View File

@@ -17,7 +17,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views
public MeasureToolPage()
{
var settingsUtils = new SettingsUtils();
var settingsUtils = SettingsUtils.Default;
ViewModel = new MeasureToolViewModel(
settingsUtils,
SettingsRepository<GeneralSettings>.GetInstance(settingsUtils),

Some files were not shown because too many files have changed in this diff Show More