mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-01-27 06:27:25 +01:00
Compare commits
11 Commits
copilot/fi
...
copilot/fi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eb199d26f6 | ||
|
|
6ffa8d5827 | ||
|
|
a0ac7efd2d | ||
|
|
3882db4479 | ||
|
|
eb35b3a249 | ||
|
|
5daec13bc4 | ||
|
|
ef6f4b2c3d | ||
|
|
336cdaff9b | ||
|
|
447118ab70 | ||
|
|
7455d63bb5 | ||
|
|
bb6b36af3f |
@@ -19,6 +19,26 @@
|
||||
|
||||
class FileLocksmithModule : public PowertoyModuleIface
|
||||
{
|
||||
private:
|
||||
// Update registration based on enabled state
|
||||
void UpdateRegistration(bool enabled)
|
||||
{
|
||||
if (enabled)
|
||||
{
|
||||
#if defined(ENABLE_REGISTRATION) || defined(NDEBUG)
|
||||
FileLocksmithRuntimeRegistration::EnsureRegistered();
|
||||
Logger::info(L"File Locksmith context menu registered");
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
#if defined(ENABLE_REGISTRATION) || defined(NDEBUG)
|
||||
FileLocksmithRuntimeRegistration::Unregister();
|
||||
Logger::info(L"File Locksmith context menu unregistered");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
FileLocksmithModule()
|
||||
{
|
||||
@@ -88,21 +108,16 @@ public:
|
||||
package::RegisterSparsePackage(path, packageUri);
|
||||
}
|
||||
}
|
||||
#if defined(ENABLE_REGISTRATION) || defined(NDEBUG)
|
||||
FileLocksmithRuntimeRegistration::EnsureRegistered();
|
||||
#endif
|
||||
|
||||
m_enabled = true;
|
||||
UpdateRegistration(m_enabled);
|
||||
}
|
||||
|
||||
virtual void disable() override
|
||||
{
|
||||
Logger::info(L"File Locksmith disabled");
|
||||
#if defined(ENABLE_REGISTRATION) || defined(NDEBUG)
|
||||
FileLocksmithRuntimeRegistration::Unregister();
|
||||
Logger::info(L"File Locksmith context menu unregistered (Win10)");
|
||||
#endif
|
||||
m_enabled = false;
|
||||
UpdateRegistration(m_enabled);
|
||||
}
|
||||
|
||||
virtual bool is_enabled() override
|
||||
@@ -135,6 +150,7 @@ private:
|
||||
{
|
||||
m_enabled = FileLocksmithSettingsInstance().GetEnabled();
|
||||
m_extended_only = FileLocksmithSettingsInstance().GetShowInExtendedContextMenu();
|
||||
UpdateRegistration(m_enabled);
|
||||
Trace::EnableFileLocksmith(m_enabled);
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,26 @@
|
||||
// Note: Settings are managed via Settings and UI Settings
|
||||
class NewModule : public PowertoyModuleIface
|
||||
{
|
||||
private:
|
||||
// Update registration based on enabled state
|
||||
void UpdateRegistration(bool enabled)
|
||||
{
|
||||
if (enabled)
|
||||
{
|
||||
#if defined(ENABLE_REGISTRATION) || defined(NDEBUG)
|
||||
NewPlusRuntimeRegistration::EnsureRegisteredWin10();
|
||||
Logger::info(L"New+ context menu registered");
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
#if defined(ENABLE_REGISTRATION) || defined(NDEBUG)
|
||||
NewPlusRuntimeRegistration::Unregister();
|
||||
Logger::info(L"New+ context menu unregistered");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
NewModule()
|
||||
{
|
||||
@@ -98,14 +118,9 @@ public:
|
||||
{
|
||||
newplus::utilities::register_msix_package();
|
||||
}
|
||||
else
|
||||
{
|
||||
#if defined(ENABLE_REGISTRATION) || defined(NDEBUG)
|
||||
NewPlusRuntimeRegistration::EnsureRegisteredWin10();
|
||||
#endif
|
||||
}
|
||||
|
||||
powertoy_new_enabled = true;
|
||||
UpdateRegistration(powertoy_new_enabled);
|
||||
}
|
||||
|
||||
virtual void disable() override
|
||||
@@ -150,19 +165,14 @@ private:
|
||||
{
|
||||
Trace::EventToggleOnOff(false);
|
||||
}
|
||||
if (!package::IsWin11OrGreater())
|
||||
{
|
||||
#if defined(ENABLE_REGISTRATION) || defined(NDEBUG)
|
||||
NewPlusRuntimeRegistration::Unregister();
|
||||
Logger::info(L"New+ context menu unregistered (Win10)");
|
||||
#endif
|
||||
}
|
||||
powertoy_new_enabled = false;
|
||||
UpdateRegistration(powertoy_new_enabled);
|
||||
}
|
||||
|
||||
void init_settings()
|
||||
{
|
||||
powertoy_new_enabled = NewSettingsInstance().GetEnabled();
|
||||
UpdateRegistration(powertoy_new_enabled);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -49,7 +49,9 @@ namespace Awake.Core
|
||||
|
||||
private static DateTimeOffset ExpireAt { get; set; }
|
||||
|
||||
private static readonly CompositeFormat AwakeMinute = CompositeFormat.Parse(Resources.AWAKE_MINUTE);
|
||||
private static readonly CompositeFormat AwakeMinutes = CompositeFormat.Parse(Resources.AWAKE_MINUTES);
|
||||
private static readonly CompositeFormat AwakeHour = CompositeFormat.Parse(Resources.AWAKE_HOUR);
|
||||
private static readonly CompositeFormat AwakeHours = CompositeFormat.Parse(Resources.AWAKE_HOURS);
|
||||
private static readonly BlockingCollection<ExecutionState> _stateQueue;
|
||||
private static CancellationTokenSource _tokenSource;
|
||||
@@ -451,7 +453,7 @@ namespace Awake.Core
|
||||
Dictionary<string, uint> optionsList = new()
|
||||
{
|
||||
{ string.Format(CultureInfo.InvariantCulture, AwakeMinutes, 30), 1800 },
|
||||
{ string.Format(CultureInfo.InvariantCulture, AwakeHours, 1), 3600 },
|
||||
{ string.Format(CultureInfo.InvariantCulture, AwakeHour, 1), 3600 },
|
||||
{ string.Format(CultureInfo.InvariantCulture, AwakeHours, 2), 7200 },
|
||||
};
|
||||
return optionsList;
|
||||
|
||||
@@ -159,6 +159,15 @@ namespace Awake.Properties {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to {0} hour.
|
||||
/// </summary>
|
||||
internal static string AWAKE_HOUR {
|
||||
get {
|
||||
return ResourceManager.GetString("AWAKE_HOUR", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to {0} hours.
|
||||
/// </summary>
|
||||
@@ -240,6 +249,15 @@ namespace Awake.Properties {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to {0} minute.
|
||||
/// </summary>
|
||||
internal static string AWAKE_MINUTE {
|
||||
get {
|
||||
return ResourceManager.GetString("AWAKE_MINUTE", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to {0} minutes.
|
||||
/// </summary>
|
||||
|
||||
@@ -123,6 +123,10 @@
|
||||
<data name="AWAKE_EXIT" xml:space="preserve">
|
||||
<value>Exit</value>
|
||||
</data>
|
||||
<data name="AWAKE_HOUR" xml:space="preserve">
|
||||
<value>{0} hour</value>
|
||||
<comment>{0} shouldn't be removed. It will be replaced by the number 1 at runtime by the application. Used for defining a period to keep the PC awake.</comment>
|
||||
</data>
|
||||
<data name="AWAKE_HOURS" xml:space="preserve">
|
||||
<value>{0} hours</value>
|
||||
<comment>{0} shouldn't be removed. It will be replaced by a number greater than 1 at runtime by the application. Used for defining a period to keep the PC awake.</comment>
|
||||
@@ -142,6 +146,10 @@
|
||||
<value>Keep awake until expiration date and time</value>
|
||||
<comment>Keep the system awake until expiration date and time</comment>
|
||||
</data>
|
||||
<data name="AWAKE_MINUTE" xml:space="preserve">
|
||||
<value>{0} minute</value>
|
||||
<comment>{0} shouldn't be removed. It will be replaced by the number 1 at runtime by the application. Used for defining a period to keep the PC awake.</comment>
|
||||
</data>
|
||||
<data name="AWAKE_MINUTES" xml:space="preserve">
|
||||
<value>{0} minutes</value>
|
||||
<comment>{0} shouldn't be removed. It will be replaced by a number greater than 1 at runtime by the application. Used for defining a period to keep the PC awake.</comment>
|
||||
|
||||
@@ -27,7 +27,9 @@ public partial class MainListPage : DynamicListPage,
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
|
||||
private readonly TopLevelCommandManager _tlcManager;
|
||||
private IEnumerable<IListItem>? _filteredItems;
|
||||
private IEnumerable<Scored<IListItem>>? _filteredItems;
|
||||
private IEnumerable<Scored<IListItem>>? _filteredApps;
|
||||
private IEnumerable<IListItem>? _allApps;
|
||||
private bool _includeApps;
|
||||
private bool _filteredItemsIncludesApps;
|
||||
|
||||
@@ -83,7 +85,7 @@ public partial class MainListPage : DynamicListPage,
|
||||
}
|
||||
else
|
||||
{
|
||||
RaiseItemsChanged(_tlcManager.TopLevelCommands.Count);
|
||||
RaiseItemsChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,7 +150,13 @@ public partial class MainListPage : DynamicListPage,
|
||||
{
|
||||
lock (_tlcManager.TopLevelCommands)
|
||||
{
|
||||
return _filteredItems?.ToArray() ?? [];
|
||||
var items = Enumerable.Empty<Scored<IListItem>>()
|
||||
.Concat(_filteredItems is not null ? _filteredItems : [])
|
||||
.Concat(_filteredApps is not null ? _filteredApps : [])
|
||||
.OrderByDescending(o => o.Score)
|
||||
.Select(s => s.Item)
|
||||
.ToArray();
|
||||
return items;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -167,6 +175,8 @@ public partial class MainListPage : DynamicListPage,
|
||||
{
|
||||
_filteredItemsIncludesApps = _includeApps;
|
||||
_filteredItems = null;
|
||||
_filteredApps = null;
|
||||
_allApps = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,6 +194,8 @@ public partial class MainListPage : DynamicListPage,
|
||||
{
|
||||
_filteredItemsIncludesApps = _includeApps;
|
||||
_filteredItems = null;
|
||||
_filteredApps = null;
|
||||
_allApps = null;
|
||||
RaiseItemsChanged(commands.Count);
|
||||
return;
|
||||
}
|
||||
@@ -193,35 +205,49 @@ public partial class MainListPage : DynamicListPage,
|
||||
if (!newSearch.StartsWith(oldSearch, StringComparison.CurrentCultureIgnoreCase))
|
||||
{
|
||||
_filteredItems = null;
|
||||
_filteredApps = null;
|
||||
_allApps = null;
|
||||
}
|
||||
|
||||
// If the internal state has changed, reset _filteredItems to reset the list.
|
||||
if (_filteredItemsIncludesApps != _includeApps)
|
||||
{
|
||||
_filteredItems = null;
|
||||
_filteredApps = null;
|
||||
_allApps = null;
|
||||
}
|
||||
|
||||
var newFilteredItems = _filteredItems?.Select(s => s.Item);
|
||||
|
||||
// If we don't have any previous filter results to work with, start
|
||||
// with a list of all our commands & apps.
|
||||
if (_filteredItems is null)
|
||||
if (newFilteredItems is null && _filteredApps is null)
|
||||
{
|
||||
_filteredItems = commands;
|
||||
newFilteredItems = commands;
|
||||
_filteredItemsIncludesApps = _includeApps;
|
||||
|
||||
if (_includeApps)
|
||||
{
|
||||
IEnumerable<IListItem> apps = AllAppsCommandProvider.Page.GetItems();
|
||||
var appIds = apps.Select(app => app.Command.Id).ToArray();
|
||||
|
||||
// Remove any top level pinned apps and use the apps from AllAppsCommandProvider.Page.GetItems()
|
||||
// since they contain details.
|
||||
_filteredItems = _filteredItems.Where(item => item.Command is not AppCommand);
|
||||
_filteredItems = _filteredItems.Concat(apps);
|
||||
_allApps = AllAppsCommandProvider.Page.GetItems();
|
||||
}
|
||||
}
|
||||
|
||||
// Produce a list of everything that matches the current filter.
|
||||
_filteredItems = ListHelpers.FilterList<IListItem>(_filteredItems, SearchText, ScoreTopLevelItem);
|
||||
RaiseItemsChanged(_filteredItems.Count());
|
||||
_filteredItems = ListHelpers.FilterListWithScores<IListItem>(newFilteredItems ?? [], SearchText, ScoreTopLevelItem);
|
||||
|
||||
// Produce a list of filtered apps with the appropriate limit
|
||||
if (_allApps is not null)
|
||||
{
|
||||
_filteredApps = ListHelpers.FilterListWithScores<IListItem>(_allApps, SearchText, ScoreTopLevelItem);
|
||||
|
||||
var appResultLimit = AllAppsCommandProvider.TopLevelResultLimit;
|
||||
if (appResultLimit >= 0)
|
||||
{
|
||||
_filteredApps = _filteredApps.Take(appResultLimit);
|
||||
}
|
||||
}
|
||||
|
||||
RaiseItemsChanged();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,9 +3,7 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.CmdPal.Ext.Apps.Programs;
|
||||
using Microsoft.CmdPal.Ext.Apps.Properties;
|
||||
using Microsoft.CmdPal.Ext.Apps.State;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
@@ -45,6 +43,28 @@ public partial class AllAppsCommandProvider : CommandProvider
|
||||
PinnedAppsManager.Instance.PinStateChanged += OnPinStateChanged;
|
||||
}
|
||||
|
||||
public static int TopLevelResultLimit
|
||||
{
|
||||
get
|
||||
{
|
||||
var limitSetting = AllAppsSettings.Instance.SearchResultLimit;
|
||||
|
||||
if (limitSetting is null)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
var quantity = -1;
|
||||
|
||||
if (int.TryParse(limitSetting, out var result))
|
||||
{
|
||||
quantity = result;
|
||||
}
|
||||
|
||||
return quantity;
|
||||
}
|
||||
}
|
||||
|
||||
public override ICommandItem[] TopLevelCommands() => [_listItem, .._page.GetPinnedApps()];
|
||||
|
||||
public ICommandItem? LookupApp(string displayName)
|
||||
|
||||
@@ -20,6 +20,16 @@ public class AllAppsSettings : JsonSettingsManager, ISettingsInterface
|
||||
|
||||
private static string Experimental(string propertyName) => $"{_namespace}.experimental.{propertyName}";
|
||||
|
||||
private static readonly List<ChoiceSetSetting.Choice> _searchResultLimitChoices =
|
||||
[
|
||||
new ChoiceSetSetting.Choice(Resources.limit_none, "-1"),
|
||||
new ChoiceSetSetting.Choice(Resources.limit_0, "0"),
|
||||
new ChoiceSetSetting.Choice(Resources.limit_1, "1"),
|
||||
new ChoiceSetSetting.Choice(Resources.limit_5, "5"),
|
||||
new ChoiceSetSetting.Choice(Resources.limit_10, "10"),
|
||||
new ChoiceSetSetting.Choice(Resources.limit_20, "20"),
|
||||
];
|
||||
|
||||
#pragma warning disable SA1401 // Fields should be private
|
||||
internal static AllAppsSettings Instance = new();
|
||||
#pragma warning restore SA1401 // Fields should be private
|
||||
@@ -42,6 +52,14 @@ public class AllAppsSettings : JsonSettingsManager, ISettingsInterface
|
||||
|
||||
public bool EnablePathEnvironmentVariableSource => _enablePathEnvironmentVariableSource.Value;
|
||||
|
||||
private readonly ChoiceSetSetting _searchResultLimitSource = new(
|
||||
Namespaced(nameof(SearchResultLimit)),
|
||||
Resources.limit_fallback_results_source,
|
||||
Resources.limit_fallback_results_source_description,
|
||||
_searchResultLimitChoices);
|
||||
|
||||
public string SearchResultLimit => _searchResultLimitSource.Value ?? string.Empty;
|
||||
|
||||
private readonly ToggleSetting _enableStartMenuSource = new(
|
||||
Namespaced(nameof(EnableStartMenuSource)),
|
||||
Resources.enable_start_menu_source,
|
||||
@@ -87,6 +105,7 @@ public class AllAppsSettings : JsonSettingsManager, ISettingsInterface
|
||||
Settings.Add(_enableDesktopSource);
|
||||
Settings.Add(_enableRegistrySource);
|
||||
Settings.Add(_enablePathEnvironmentVariableSource);
|
||||
Settings.Add(_searchResultLimitSource);
|
||||
|
||||
// Load settings from file upon initialization
|
||||
LoadSettings();
|
||||
|
||||
@@ -159,6 +159,78 @@ namespace Microsoft.CmdPal.Ext.Apps.Properties {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to 0.
|
||||
/// </summary>
|
||||
internal static string limit_0 {
|
||||
get {
|
||||
return ResourceManager.GetString("limit_0", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to 1.
|
||||
/// </summary>
|
||||
internal static string limit_1 {
|
||||
get {
|
||||
return ResourceManager.GetString("limit_1", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to 10.
|
||||
/// </summary>
|
||||
internal static string limit_10 {
|
||||
get {
|
||||
return ResourceManager.GetString("limit_10", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to 20.
|
||||
/// </summary>
|
||||
internal static string limit_20 {
|
||||
get {
|
||||
return ResourceManager.GetString("limit_20", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to 5.
|
||||
/// </summary>
|
||||
internal static string limit_5 {
|
||||
get {
|
||||
return ResourceManager.GetString("limit_5", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Limit the number of applications returned from the top level.
|
||||
/// </summary>
|
||||
internal static string limit_fallback_results_source {
|
||||
get {
|
||||
return ResourceManager.GetString("limit_fallback_results_source", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Limit fallback results to n apps.
|
||||
/// </summary>
|
||||
internal static string limit_fallback_results_source_description {
|
||||
get {
|
||||
return ResourceManager.GetString("limit_fallback_results_source_description", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to None.
|
||||
/// </summary>
|
||||
internal static string limit_none {
|
||||
get {
|
||||
return ResourceManager.GetString("limit_none", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Open containing folder.
|
||||
/// </summary>
|
||||
|
||||
@@ -198,4 +198,28 @@
|
||||
<data name="unpin_app" xml:space="preserve">
|
||||
<value>Unpin</value>
|
||||
</data>
|
||||
</root>
|
||||
<data name="limit_1" xml:space="preserve">
|
||||
<value>1</value>
|
||||
</data>
|
||||
<data name="limit_5" xml:space="preserve">
|
||||
<value>5</value>
|
||||
</data>
|
||||
<data name="limit_10" xml:space="preserve">
|
||||
<value>10</value>
|
||||
</data>
|
||||
<data name="limit_20" xml:space="preserve">
|
||||
<value>20</value>
|
||||
</data>
|
||||
<data name="limit_fallback_results_source" xml:space="preserve">
|
||||
<value>Limit the number of applications returned from the top level</value>
|
||||
</data>
|
||||
<data name="limit_fallback_results_source_description" xml:space="preserve">
|
||||
<value>Limit fallback results to n apps</value>
|
||||
</data>
|
||||
<data name="limit_0" xml:space="preserve">
|
||||
<value>0</value>
|
||||
</data>
|
||||
<data name="limit_none" xml:space="preserve">
|
||||
<value>Unlimited</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -43,13 +43,18 @@ public partial class ListHelpers
|
||||
}
|
||||
|
||||
public static IEnumerable<T> FilterList<T>(IEnumerable<T> items, string query, Func<string, T, int> scoreFunction)
|
||||
{
|
||||
return FilterListWithScores<T>(items, query, scoreFunction)
|
||||
.Select(score => score.Item);
|
||||
}
|
||||
|
||||
public static IEnumerable<Scored<T>> FilterListWithScores<T>(IEnumerable<T> items, string query, Func<string, T, int> scoreFunction)
|
||||
{
|
||||
var scores = items
|
||||
.Select(li => new Scored<T>() { Item = li, Score = scoreFunction(query, li) })
|
||||
.Where(score => score.Score > 0)
|
||||
.OrderByDescending(score => score.Score);
|
||||
return scores
|
||||
.Select(score => score.Item);
|
||||
return scores;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -372,20 +372,17 @@ namespace UITests_FancyZones
|
||||
// launch FancyZones settings page
|
||||
private void LaunchFancyZones()
|
||||
{
|
||||
if (this.FindAll<NavigationViewItem>("FancyZones").Count == 0)
|
||||
{
|
||||
this.Find<NavigationViewItem>("Windowing & Layouts").Click();
|
||||
}
|
||||
this.Find<NavigationViewItem>(By.AccessibilityId("WindowingAndLayoutsNavItem")).Click();
|
||||
|
||||
this.Find<NavigationViewItem>("FancyZones").Click();
|
||||
this.Find<ToggleSwitch>("Enable FancyZones").Toggle(true);
|
||||
this.Find<NavigationViewItem>(By.AccessibilityId("FancyZonesNavItem")).Click();
|
||||
this.Find<ToggleSwitch>(By.AccessibilityId("EnableFancyZonesToggleSwitch")).Toggle(true);
|
||||
|
||||
this.Session.SetMainWindowSize(WindowSize.Large);
|
||||
Find<Element>(By.AccessibilityId("HeaderPresenter")).Click();
|
||||
this.Scroll(6, "Down"); // Pull the settings page up to make sure the settings are visible
|
||||
ZoneBehaviourSettings(TestContext.TestName);
|
||||
|
||||
this.Find<Microsoft.PowerToys.UITest.Button>("Launch layout editor").Click(false, 500, 10000);
|
||||
this.Find<Microsoft.PowerToys.UITest.Button>(By.AccessibilityId("LaunchLayoutEditorButton")).Click(false, 500, 10000);
|
||||
this.Session.Attach(PowerToysModule.FancyZone);
|
||||
|
||||
// pipeline machine may have an unstable delays, causing the custom layout to be unavailable as we set. then A retry is required.
|
||||
@@ -403,7 +400,7 @@ namespace UITests_FancyZones
|
||||
this.Find<Microsoft.PowerToys.UITest.Button>("Close").Click();
|
||||
this.Session.Attach(PowerToysModule.PowerToysSettings);
|
||||
SetupCustomLayouts();
|
||||
this.Find<Microsoft.PowerToys.UITest.Button>("Launch layout editor").Click(false, 5000, 5000);
|
||||
this.Find<Microsoft.PowerToys.UITest.Button>(By.AccessibilityId("LaunchLayoutEditorButton")).Click(false, 5000, 5000);
|
||||
this.Session.Attach(PowerToysModule.FancyZone);
|
||||
this.Find<Microsoft.PowerToys.UITest.Button>("Maximize").Click();
|
||||
|
||||
|
||||
@@ -43,11 +43,32 @@ private:
|
||||
//contains the non localized key of the powertoy
|
||||
std::wstring app_key;
|
||||
|
||||
// Update registration based on enabled state
|
||||
void UpdateRegistration(bool enabled)
|
||||
{
|
||||
if (enabled)
|
||||
{
|
||||
#if defined(ENABLE_REGISTRATION) || defined(NDEBUG)
|
||||
ImageResizerRuntimeRegistration::EnsureRegistered();
|
||||
Logger::info(L"ImageResizer context menu registered");
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
#if defined(ENABLE_REGISTRATION) || defined(NDEBUG)
|
||||
ImageResizerRuntimeRegistration::Unregister();
|
||||
Logger::info(L"ImageResizer context menu unregistered");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public:
|
||||
// Constructor
|
||||
ImageResizerModule()
|
||||
{
|
||||
m_enabled = CSettingsInstance().GetEnabled();
|
||||
UpdateRegistration(m_enabled);
|
||||
app_name = GET_RESOURCE_STRING(IDS_IMAGERESIZER);
|
||||
app_key = ImageResizerConstants::ModuleKey;
|
||||
LoggerHelpers::init_logger(app_key, L"ModuleInterface", LogSettings::imageResizerLoggerName);
|
||||
@@ -112,10 +133,7 @@ public:
|
||||
package::RegisterSparsePackage(path, packageUri);
|
||||
}
|
||||
}
|
||||
#if defined(ENABLE_REGISTRATION) || defined(NDEBUG)
|
||||
ImageResizerRuntimeRegistration::EnsureRegistered();
|
||||
#endif
|
||||
|
||||
UpdateRegistration(m_enabled);
|
||||
Trace::EnableImageResizer(m_enabled);
|
||||
}
|
||||
|
||||
@@ -123,11 +141,8 @@ public:
|
||||
virtual void disable()
|
||||
{
|
||||
m_enabled = false;
|
||||
UpdateRegistration(m_enabled);
|
||||
Trace::EnableImageResizer(m_enabled);
|
||||
#if defined(ENABLE_REGISTRATION) || defined(NDEBUG)
|
||||
ImageResizerRuntimeRegistration::Unregister();
|
||||
Logger::info(L"ImageResizer context menu unregistered (Win10)");
|
||||
#endif
|
||||
}
|
||||
|
||||
// Returns if the powertoys is enabled
|
||||
|
||||
@@ -168,6 +168,25 @@ private:
|
||||
//contains the non localized key of the powertoy
|
||||
std::wstring app_key;
|
||||
|
||||
// Update registration based on enabled state
|
||||
void UpdateRegistration(bool enabled)
|
||||
{
|
||||
if (enabled)
|
||||
{
|
||||
#if defined(ENABLE_REGISTRATION) || defined(NDEBUG)
|
||||
PowerRenameRuntimeRegistration::EnsureRegistered();
|
||||
Logger::info(L"PowerRename context menu registered");
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
#if defined(ENABLE_REGISTRATION) || defined(NDEBUG)
|
||||
PowerRenameRuntimeRegistration::Unregister();
|
||||
Logger::info(L"PowerRename context menu unregistered");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
// Return the localized display name of the powertoy
|
||||
virtual PCWSTR get_name() override
|
||||
@@ -202,9 +221,7 @@ public:
|
||||
package::RegisterSparsePackage(path, packageUri);
|
||||
}
|
||||
}
|
||||
#if defined(ENABLE_REGISTRATION) || defined(NDEBUG)
|
||||
PowerRenameRuntimeRegistration::EnsureRegistered();
|
||||
#endif
|
||||
UpdateRegistration(m_enabled);
|
||||
}
|
||||
|
||||
// Disable the powertoy
|
||||
@@ -212,10 +229,7 @@ public:
|
||||
{
|
||||
m_enabled = false;
|
||||
Logger::info(L"PowerRename disabled");
|
||||
#if defined(ENABLE_REGISTRATION) || defined(NDEBUG)
|
||||
PowerRenameRuntimeRegistration::Unregister();
|
||||
Logger::info(L"PowerRename context menu unregistered (Win10)");
|
||||
#endif
|
||||
UpdateRegistration(m_enabled);
|
||||
}
|
||||
|
||||
// Returns if the powertoy is enabled
|
||||
@@ -315,6 +329,7 @@ public:
|
||||
void init_settings()
|
||||
{
|
||||
m_enabled = CSettingsInstance().GetEnabled();
|
||||
UpdateRegistration(m_enabled);
|
||||
Trace::EnablePowerRename(m_enabled);
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,13 @@ namespace Microsoft.PowerToys.Tools.XamlIndexBuilder
|
||||
"ShellPage.xaml",
|
||||
};
|
||||
|
||||
// Hardcoded panel-to-page mapping (temporary until generic panel host mapping is needed)
|
||||
// Key: panel file base name (without .xaml), Value: owning page base name
|
||||
private static readonly Dictionary<string, string> PanelPageMapping = new(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
{ "MouseJumpPanel", "MouseUtilsPage" },
|
||||
};
|
||||
|
||||
private static JsonSerializerOptions serializeOption = new()
|
||||
{
|
||||
WriteIndented = true,
|
||||
@@ -33,32 +40,117 @@ namespace Microsoft.PowerToys.Tools.XamlIndexBuilder
|
||||
Environment.Exit(1);
|
||||
}
|
||||
|
||||
string xamlDirectory = args[0];
|
||||
string xamlRootDirectory = args[0];
|
||||
string outputFile = args[1];
|
||||
|
||||
if (!Directory.Exists(xamlDirectory))
|
||||
if (!Directory.Exists(xamlRootDirectory))
|
||||
{
|
||||
Debug.WriteLine($"Error: Directory '{xamlDirectory}' does not exist.");
|
||||
Debug.WriteLine($"Error: Directory '{xamlRootDirectory}' does not exist.");
|
||||
Environment.Exit(1);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var searchableElements = new List<SettingEntry>();
|
||||
var xamlFiles = Directory.GetFiles(xamlDirectory, "*.xaml", SearchOption.AllDirectories);
|
||||
var processedFiles = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
foreach (var xamlFile in xamlFiles)
|
||||
void ScanDirectory(string root)
|
||||
{
|
||||
var fileName = Path.GetFileName(xamlFile);
|
||||
if (ExcludedXamlFiles.Contains(fileName))
|
||||
if (!Directory.Exists(root))
|
||||
{
|
||||
// Skip ShellPage.xaml as it contains many elements not relevant for search
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
|
||||
Debug.WriteLine($"Processing: {fileName}");
|
||||
var elements = ExtractSearchableElements(xamlFile);
|
||||
searchableElements.AddRange(elements);
|
||||
Debug.WriteLine($"[XamlIndexBuilder] Scanning root: {root}");
|
||||
var xamlFilesLocal = Directory.GetFiles(root, "*.xaml", SearchOption.AllDirectories);
|
||||
foreach (var xamlFile in xamlFilesLocal)
|
||||
{
|
||||
var fullPath = Path.GetFullPath(xamlFile);
|
||||
if (processedFiles.Contains(fullPath))
|
||||
{
|
||||
continue; // already handled (can happen if overlapping directories)
|
||||
}
|
||||
|
||||
var fileName = Path.GetFileName(xamlFile);
|
||||
if (ExcludedXamlFiles.Contains(fileName))
|
||||
{
|
||||
continue; // explicitly excluded
|
||||
}
|
||||
|
||||
Debug.WriteLine($"Processing: {fileName}");
|
||||
var elements = ExtractSearchableElements(xamlFile);
|
||||
|
||||
// Apply hardcoded panel mapping override
|
||||
var baseName = Path.GetFileNameWithoutExtension(xamlFile);
|
||||
if (PanelPageMapping.TryGetValue(baseName, out var hostPage))
|
||||
{
|
||||
for (int i = 0; i < elements.Count; i++)
|
||||
{
|
||||
var entry = elements[i];
|
||||
entry.PageTypeName = hostPage;
|
||||
elements[i] = entry;
|
||||
}
|
||||
}
|
||||
|
||||
searchableElements.AddRange(elements);
|
||||
processedFiles.Add(fullPath);
|
||||
}
|
||||
}
|
||||
|
||||
// Scan well-known subdirectories under the provided root
|
||||
var subDirs = new[] { "Views", "Panels" };
|
||||
foreach (var sub in subDirs)
|
||||
{
|
||||
ScanDirectory(Path.Combine(xamlRootDirectory, sub));
|
||||
}
|
||||
|
||||
// Fallback: also scan root directly (in case some XAML lives at root level)
|
||||
ScanDirectory(xamlRootDirectory);
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Explicit include section: add specific XAML files that we always want indexed
|
||||
// even if future logic excludes them or they live outside typical scan patterns.
|
||||
// Add future files to the ExplicitExtraXamlFiles array below.
|
||||
// -----------------------------------------------------------------------------
|
||||
string[] explicitExtraXamlFiles = new[]
|
||||
{
|
||||
"MouseJumpPanel.xaml", // Mouse Jump settings panel
|
||||
};
|
||||
|
||||
foreach (var extraFileName in explicitExtraXamlFiles)
|
||||
{
|
||||
try
|
||||
{
|
||||
var matches = Directory.GetFiles(xamlRootDirectory, extraFileName, SearchOption.AllDirectories);
|
||||
foreach (var match in matches)
|
||||
{
|
||||
var full = Path.GetFullPath(match);
|
||||
if (processedFiles.Contains(full))
|
||||
{
|
||||
continue; // already processed in general scan
|
||||
}
|
||||
|
||||
Debug.WriteLine($"Processing (explicit include): {extraFileName}");
|
||||
var elements = ExtractSearchableElements(full);
|
||||
var baseName = Path.GetFileNameWithoutExtension(full);
|
||||
if (PanelPageMapping.TryGetValue(baseName, out var hostPage))
|
||||
{
|
||||
for (int i = 0; i < elements.Count; i++)
|
||||
{
|
||||
var entry = elements[i];
|
||||
entry.PageTypeName = hostPage;
|
||||
elements[i] = entry;
|
||||
}
|
||||
}
|
||||
|
||||
searchableElements.AddRange(elements);
|
||||
processedFiles.Add(full);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"Explicit include failed for {extraFileName}: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
searchableElements = searchableElements.OrderBy(e => e.PageTypeName).ThenBy(e => e.ElementName).ToList();
|
||||
@@ -97,15 +189,15 @@ namespace Microsoft.PowerToys.Tools.XamlIndexBuilder
|
||||
.Where(e => e.Name.LocalName == "SettingsPageControl")
|
||||
.Where(e => e.Attribute(x + "Uid") != null);
|
||||
|
||||
// Extract SettingsCard elements
|
||||
// Extract SettingsCard elements (support both Name and x:Name)
|
||||
var settingsElements = doc.Descendants()
|
||||
.Where(e => e.Name.LocalName == "SettingsCard")
|
||||
.Where(e => e.Attribute("Name") != null || e.Attribute(x + "Uid") != null);
|
||||
.Where(e => e.Attribute("Name") != null || e.Attribute(x + "Name") != null || e.Attribute(x + "Uid") != null);
|
||||
|
||||
// Extract SettingsExpander elements
|
||||
// Extract SettingsExpander elements (support both Name and x:Name)
|
||||
var settingsExpanderElements = doc.Descendants()
|
||||
.Where(e => e.Name.LocalName == "SettingsExpander")
|
||||
.Where(e => e.Attribute("Name") != null || e.Attribute(x + "Uid") != null);
|
||||
.Where(e => e.Attribute("Name") != null || e.Attribute(x + "Name") != null || e.Attribute(x + "Uid") != null);
|
||||
|
||||
// Process SettingsPageControl elements
|
||||
foreach (var element in settingsPageElements)
|
||||
@@ -185,16 +277,36 @@ namespace Microsoft.PowerToys.Tools.XamlIndexBuilder
|
||||
|
||||
public static string GetElementName(XElement element, XNamespace x)
|
||||
{
|
||||
// Get Name attribute (we call it ElementName in our indexing system)
|
||||
var name = element.Attribute("Name")?.Value;
|
||||
if (string.IsNullOrEmpty(name))
|
||||
{
|
||||
name = element.Attribute(x + "Name")?.Value;
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
public static string GetElementUid(XElement element, XNamespace x)
|
||||
{
|
||||
// Try x:Uid
|
||||
// Try x:Uid on the element itself
|
||||
var uid = element.Attribute(x + "Uid")?.Value;
|
||||
return uid;
|
||||
if (!string.IsNullOrWhiteSpace(uid))
|
||||
{
|
||||
return uid;
|
||||
}
|
||||
|
||||
// Fallback: check the first direct child element's x:Uid
|
||||
var firstChild = element.Elements().FirstOrDefault();
|
||||
if (firstChild != null)
|
||||
{
|
||||
var childUid = firstChild.Attribute(x + "Uid")?.Value;
|
||||
if (!string.IsNullOrWhiteSpace(childUid))
|
||||
{
|
||||
return childUid;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static string GetParentElementName(XElement element, XNamespace x)
|
||||
@@ -211,6 +323,11 @@ namespace Microsoft.PowerToys.Tools.XamlIndexBuilder
|
||||
if (expanderParent?.Name.LocalName == "SettingsExpander")
|
||||
{
|
||||
var expanderName = expanderParent.Attribute("Name")?.Value;
|
||||
if (string.IsNullOrEmpty(expanderName))
|
||||
{
|
||||
expanderName = expanderParent.Attribute(x + "Name")?.Value;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(expanderName))
|
||||
{
|
||||
return expanderName;
|
||||
@@ -221,6 +338,11 @@ namespace Microsoft.PowerToys.Tools.XamlIndexBuilder
|
||||
{
|
||||
// Direct child of SettingsExpander
|
||||
var expanderName = current.Attribute("Name")?.Value;
|
||||
if (string.IsNullOrEmpty(expanderName))
|
||||
{
|
||||
expanderName = current.Attribute(x + "Name")?.Value;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(expanderName))
|
||||
{
|
||||
return expanderName;
|
||||
|
||||
@@ -29,16 +29,15 @@
|
||||
<!-- Remove UI library reference to avoid pulling WindowsDesktop runtime (WindowsBase) -->
|
||||
|
||||
<PropertyGroup>
|
||||
<!-- Fallback to dotnet if not provided by the environment -->
|
||||
<DotNetExe Condition="'$(DotNetExe)' == ''">dotnet</DotNetExe>
|
||||
<XamlViewsDir Condition="'$(XamlViewsDir)' == ''">$(MSBuildProjectDirectory)\..\Settings.UI\SettingsXAML\Views</XamlViewsDir>
|
||||
<XamlRootDir Condition="'$(XamlRootDir)' == ''">$(MSBuildProjectDirectory)\..\Settings.UI\SettingsXAML</XamlRootDir>
|
||||
<XamlRootDir Condition="'$(XamlViewsDir)' != ''">$([System.IO.Path]::GetDirectoryName('$(XamlViewsDir)'))</XamlRootDir>
|
||||
<GeneratedJsonFile Condition="'$(GeneratedJsonFile)' == ''">$(MSBuildProjectDirectory)\..\Settings.UI\Assets\Settings\search.index.json</GeneratedJsonFile>
|
||||
</PropertyGroup>
|
||||
<Target Name="GenerateSearchIndexSelf" AfterTargets="Build">
|
||||
<RemoveDir Directories="$(MSBuildProjectDirectory)\obj\ARM64;$(MSBuildProjectDirectory)\obj\x64;$(MSBuildProjectDirectory)\bin" />
|
||||
<MakeDir Directories="$([System.IO.Path]::GetDirectoryName('$(GeneratedJsonFile)'))" />
|
||||
<Message Importance="high" Text="[XamlIndexBuilder] Generating search index. Views='$(XamlViewsDir)'; Out='$(GeneratedJsonFile)'; Tool='$(TargetPath)'; DotNet='$(DotNetExe)'." />
|
||||
<!-- Execute via dotnet so host architecture doesn't need to match -->
|
||||
<Exec Command=""$(DotNetExe)" "$(TargetPath)" "$(XamlViewsDir)" "$(GeneratedJsonFile)"" />
|
||||
<Message Importance="high" Text="[XamlIndexBuilder] Generating search index. Root='$(XamlRootDir)'; Out='$(GeneratedJsonFile)'; Tool='$(TargetPath)'; DotNet='$(DotNetExe)'." />
|
||||
<Exec Command=""$(DotNetExe)" "$(TargetPath)" "$(XamlRootDir)" "$(GeneratedJsonFile)"" />
|
||||
</Target>
|
||||
</Project>
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace Microsoft.PowerToys.Settings.UI.Helpers;
|
||||
public abstract partial class NavigablePage : Page
|
||||
{
|
||||
private const int ExpandWaitDuration = 500;
|
||||
private const int AnimationDuration = 2000;
|
||||
private const int AnimationDuration = 1850;
|
||||
|
||||
private NavigationParams _pendingNavigationParams;
|
||||
|
||||
@@ -80,6 +80,9 @@ public abstract partial class NavigablePage : Page
|
||||
return;
|
||||
}
|
||||
|
||||
// Attempt to set keyboard focus so that screen readers announce the element and keyboard users land directly on it.
|
||||
TrySetFocus(target);
|
||||
|
||||
// Get the visual and compositor
|
||||
var visual = ElementCompositionPreview.GetElementVisual(target);
|
||||
var compositor = visual.Compositor;
|
||||
@@ -92,9 +95,9 @@ public abstract partial class NavigablePage : Page
|
||||
dropShadow.Offset = new Vector3(0, 0, 0);
|
||||
|
||||
var spriteVisual = compositor.CreateSpriteVisual();
|
||||
spriteVisual.Size = new Vector2((float)target.ActualWidth, (float)target.ActualHeight);
|
||||
spriteVisual.Size = new Vector2((float)target.ActualWidth + 8, (float)target.ActualHeight + 8);
|
||||
spriteVisual.Shadow = dropShadow;
|
||||
spriteVisual.Offset = new Vector3(0, 0, 0);
|
||||
spriteVisual.Offset = new Vector3(-4, -4, 0);
|
||||
|
||||
// Insert the shadow visual behind the target element
|
||||
ElementCompositionPreview.SetElementChildVisual(target, spriteVisual);
|
||||
@@ -113,9 +116,129 @@ public abstract partial class NavigablePage : Page
|
||||
ElementCompositionPreview.SetElementChildVisual(target, null);
|
||||
}
|
||||
|
||||
private static void TrySetFocus(FrameworkElement target)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Prefer Control.Focus when available.
|
||||
if (target is Control ctrl)
|
||||
{
|
||||
// Ensure it can receive focus.
|
||||
if (!ctrl.IsTabStop)
|
||||
{
|
||||
ctrl.IsTabStop = true;
|
||||
}
|
||||
|
||||
ctrl.Focus(FocusState.Programmatic);
|
||||
}
|
||||
|
||||
// Target is not a Control. Find first focusable descendant Control.
|
||||
var focusCandidate = FindFirstFocusableDescendant(target);
|
||||
if (focusCandidate != null)
|
||||
{
|
||||
if (!focusCandidate.IsTabStop)
|
||||
{
|
||||
focusCandidate.IsTabStop = true;
|
||||
}
|
||||
|
||||
focusCandidate.Focus(FocusState.Programmatic);
|
||||
return;
|
||||
}
|
||||
|
||||
// Fallback: attempt to focus parent control if no descendant found.
|
||||
if (target.Parent is Control parent)
|
||||
{
|
||||
if (!parent.IsTabStop)
|
||||
{
|
||||
parent.IsTabStop = true;
|
||||
}
|
||||
|
||||
parent.Focus(FocusState.Programmatic);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Swallow focus exceptions; not critical. Could log if logging enabled.
|
||||
// Leave the default focus as it is.
|
||||
}
|
||||
}
|
||||
|
||||
private static Control FindFirstFocusableDescendant(FrameworkElement root)
|
||||
{
|
||||
if (root == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var queue = new System.Collections.Generic.Queue<DependencyObject>();
|
||||
queue.Enqueue(root);
|
||||
while (queue.Count > 0)
|
||||
{
|
||||
var current = queue.Dequeue();
|
||||
if (current is Control c && c.IsEnabled && c.Visibility == Visibility.Visible)
|
||||
{
|
||||
return c;
|
||||
}
|
||||
|
||||
int count = VisualTreeHelper.GetChildrenCount(current);
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
queue.Enqueue(VisualTreeHelper.GetChild(current, i));
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected FrameworkElement FindElementByName(string name)
|
||||
{
|
||||
var element = this.FindName(name) as FrameworkElement;
|
||||
return element;
|
||||
if (element != null)
|
||||
{
|
||||
return element;
|
||||
}
|
||||
|
||||
if (this.Content is DependencyObject root)
|
||||
{
|
||||
var found = FindInDescendants(root, name);
|
||||
if (found != null)
|
||||
{
|
||||
return found;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static FrameworkElement FindInDescendants(DependencyObject root, string name)
|
||||
{
|
||||
if (root == null || string.IsNullOrEmpty(name))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var queue = new System.Collections.Generic.Queue<DependencyObject>();
|
||||
queue.Enqueue(root);
|
||||
while (queue.Count > 0)
|
||||
{
|
||||
var current = queue.Dequeue();
|
||||
if (current is FrameworkElement fe)
|
||||
{
|
||||
var local = fe.FindName(name) as FrameworkElement;
|
||||
if (local != null)
|
||||
{
|
||||
return local;
|
||||
}
|
||||
}
|
||||
|
||||
int count = VisualTreeHelper.GetChildrenCount(current);
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var child = VisualTreeHelper.GetChild(current, i);
|
||||
queue.Enqueue(child);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,9 +181,6 @@
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Removed nested publish/exec and copy targets. -->
|
||||
|
||||
<!-- Build XamlIndexBuilder before compiling Settings to ensure the search index exists without taking a project reference. -->
|
||||
<Target Name="BuildXamlIndexBeforeSettings" BeforeTargets="CoreCompile">
|
||||
<Message Importance="high" Text="[Settings] Building XamlIndexBuilder prior to compile. Views='$(MSBuildProjectDirectory)\SettingsXAML\Views' Out='$(GeneratedJsonFile)'" />
|
||||
<MSBuild Projects="..\Settings.UI.XamlIndexBuilder\Settings.UI.XamlIndexBuilder.csproj" Targets="Build" Properties="Configuration=$(Configuration);Platform=Any CPU;TargetFramework=net9.0;XamlViewsDir=$(MSBuildProjectDirectory)\SettingsXAML\Views;GeneratedJsonFile=$(GeneratedJsonFile)" />
|
||||
|
||||
@@ -6,6 +6,8 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
using ManagedCommon;
|
||||
using Microsoft.PowerToys.Settings.UI.Helpers;
|
||||
using Microsoft.PowerToys.Settings.UI.Library;
|
||||
using Microsoft.PowerToys.Settings.UI.Library.HotkeyConflicts;
|
||||
|
||||
@@ -14,6 +16,7 @@ namespace Microsoft.PowerToys.Settings.UI.Services
|
||||
public class GlobalHotkeyConflictManager
|
||||
{
|
||||
private readonly Func<string, int> _sendIPCMessage;
|
||||
private GeneralSettings _generalSettings;
|
||||
|
||||
private static GlobalHotkeyConflictManager _instance;
|
||||
private AllHotkeyConflictsData _currentConflicts = new AllHotkeyConflictsData();
|
||||
@@ -34,6 +37,11 @@ namespace Microsoft.PowerToys.Settings.UI.Services
|
||||
|
||||
public event EventHandler<AllHotkeyConflictsEventArgs> ConflictsUpdated;
|
||||
|
||||
public void UpdateGeneralSettings(GeneralSettings generalSettings)
|
||||
{
|
||||
_generalSettings = generalSettings;
|
||||
}
|
||||
|
||||
public void RequestAllConflicts()
|
||||
{
|
||||
var requestMessage = "{\"get_all_hotkey_conflicts\":{}}";
|
||||
@@ -42,8 +50,76 @@ namespace Microsoft.PowerToys.Settings.UI.Services
|
||||
|
||||
private void OnAllHotkeyConflictsReceived(object sender, AllHotkeyConflictsEventArgs e)
|
||||
{
|
||||
_currentConflicts = e.Conflicts;
|
||||
ConflictsUpdated?.Invoke(this, e);
|
||||
_currentConflicts = FilterConflictsForDisabledModules(e.Conflicts);
|
||||
ConflictsUpdated?.Invoke(this, new AllHotkeyConflictsEventArgs { Conflicts = _currentConflicts });
|
||||
}
|
||||
|
||||
private AllHotkeyConflictsData FilterConflictsForDisabledModules(AllHotkeyConflictsData conflicts)
|
||||
{
|
||||
if (_generalSettings?.Enabled == null)
|
||||
{
|
||||
return conflicts;
|
||||
}
|
||||
|
||||
var filteredConflicts = new AllHotkeyConflictsData();
|
||||
|
||||
// Filter InAppConflicts
|
||||
if (conflicts.InAppConflicts != null)
|
||||
{
|
||||
filteredConflicts.InAppConflicts = new List<HotkeyConflictGroupData>();
|
||||
foreach (var conflictGroup in conflicts.InAppConflicts)
|
||||
{
|
||||
var enabledModules = conflictGroup.Modules
|
||||
.Where(module => IsModuleEnabled(module.ModuleType))
|
||||
.ToList();
|
||||
|
||||
// Only include conflict groups that have conflicts between enabled modules
|
||||
if (enabledModules.Count > 1)
|
||||
{
|
||||
filteredConflicts.InAppConflicts.Add(new HotkeyConflictGroupData
|
||||
{
|
||||
Hotkey = conflictGroup.Hotkey,
|
||||
IsSystemConflict = conflictGroup.IsSystemConflict,
|
||||
Modules = enabledModules
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// System conflicts should always be included regardless of module state
|
||||
if (conflicts.SystemConflicts != null)
|
||||
{
|
||||
filteredConflicts.SystemConflicts = new List<HotkeyConflictGroupData>();
|
||||
foreach (var conflictGroup in conflicts.SystemConflicts)
|
||||
{
|
||||
var enabledModules = conflictGroup.Modules
|
||||
.Where(module => IsModuleEnabled(module.ModuleType))
|
||||
.ToList();
|
||||
|
||||
// Include system conflicts if there are any enabled modules involved
|
||||
if (enabledModules.Count > 0)
|
||||
{
|
||||
filteredConflicts.SystemConflicts.Add(new HotkeyConflictGroupData
|
||||
{
|
||||
Hotkey = conflictGroup.Hotkey,
|
||||
IsSystemConflict = conflictGroup.IsSystemConflict,
|
||||
Modules = enabledModules
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return filteredConflicts;
|
||||
}
|
||||
|
||||
private bool IsModuleEnabled(ModuleType moduleType)
|
||||
{
|
||||
if (_generalSettings?.Enabled == null)
|
||||
{
|
||||
return true; // If we can't determine, assume enabled to be safe
|
||||
}
|
||||
|
||||
return ModuleHelper.GetIsModuleEnabled(_generalSettings, moduleType);
|
||||
}
|
||||
|
||||
public bool HasConflictForHotkey(HotkeySettings hotkey, string moduleName, int hotkeyID)
|
||||
|
||||
@@ -170,7 +170,7 @@ namespace Microsoft.PowerToys.Settings.UI.Services
|
||||
|
||||
if (string.IsNullOrEmpty(header))
|
||||
{
|
||||
Debug.WriteLine($"[SearchIndexService] WARNING: No header localization found for ElementUid: '{elementUid}'");
|
||||
header = GetString(resourceLoader, $"{elementUid}/Content");
|
||||
}
|
||||
|
||||
return (header, description);
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
<controls:SettingsGroup x:Uid="MouseUtils_MouseJump">
|
||||
|
||||
<tkcontrols:SettingsCard
|
||||
x:Name="MouseUtilsEnableMouseJump"
|
||||
x:Uid="MouseUtils_Enable_MouseJump"
|
||||
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/MouseJump.png}"
|
||||
IsEnabled="{x:Bind ViewModel.IsJumpEnabledGpoConfigured, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}">
|
||||
@@ -42,6 +43,7 @@
|
||||
</InfoBar>
|
||||
|
||||
<tkcontrols:SettingsCard
|
||||
x:Name="MouseUtilsMouseJumpActivationShortcut"
|
||||
x:Uid="MouseUtils_MouseJump_ActivationShortcut"
|
||||
HeaderIcon="{ui:FontIcon Glyph=}"
|
||||
IsEnabled="{x:Bind ViewModel.IsMouseJumpEnabled, Mode=OneWay}">
|
||||
@@ -126,6 +128,7 @@
|
||||
</tkcontrols:SettingsCard>
|
||||
|
||||
<tkcontrols:SettingsExpander
|
||||
x:Name="MouseUtilsMouseJumpAppearance"
|
||||
x:Uid="MouseUtils_MouseJump_Appearance"
|
||||
HeaderIcon="{ui:FontIcon Glyph=}"
|
||||
IsEnabled="{x:Bind ViewModel.IsMouseJumpEnabled, Mode=OneWay}"
|
||||
|
||||
@@ -19,7 +19,11 @@
|
||||
x:Uid="FancyZones_EnableToggleControl_HeaderText"
|
||||
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/FancyZones.png}"
|
||||
IsEnabled="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}">
|
||||
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.IsEnabled, Mode=TwoWay}" />
|
||||
<ToggleSwitch
|
||||
x:Name="EnableFancyZonesToggleSwitch"
|
||||
x:Uid="ToggleSwitch"
|
||||
AutomationProperties.AutomationId="EnableFancyZonesToggleSwitch"
|
||||
IsOn="{x:Bind ViewModel.IsEnabled, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
<InfoBar
|
||||
x:Uid="GPO_SettingIsManaged"
|
||||
@@ -70,19 +74,19 @@
|
||||
x:Uid="FancyZones_ZoneBehavior_GroupSettings"
|
||||
IsExpanded="True">
|
||||
<tkcontrols:SettingsExpander.Items>
|
||||
<tkcontrols:SettingsCard ContentAlignment="Left">
|
||||
<tkcontrols:SettingsCard Name="FancyZonesShiftDragCheckBoxControlHeader" ContentAlignment="Left">
|
||||
<CheckBox x:Uid="FancyZones_ShiftDragCheckBoxControl_Header" IsChecked="{x:Bind ViewModel.ShiftDrag, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
<tkcontrols:SettingsCard ContentAlignment="Left">
|
||||
<tkcontrols:SettingsCard Name="FancyZonesMouseDragCheckBoxControlHeader" ContentAlignment="Left">
|
||||
<CheckBox x:Uid="FancyZones_MouseDragCheckBoxControl_Header" IsChecked="{x:Bind ViewModel.MouseSwitch, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
<tkcontrols:SettingsCard ContentAlignment="Left">
|
||||
<tkcontrols:SettingsCard Name="FancyZonesMouseMiddleClickSpanningMultipleZonesCheckBoxControlHeader" ContentAlignment="Left">
|
||||
<CheckBox x:Uid="FancyZones_MouseMiddleClickSpanningMultipleZonesCheckBoxControl_Header" IsChecked="{x:Bind ViewModel.MouseMiddleClickSpanningMultipleZones, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
<tkcontrols:SettingsCard ContentAlignment="Left">
|
||||
<tkcontrols:SettingsCard Name="FancyZonesShowZonesOnAllMonitorsCheckBoxControl" ContentAlignment="Left">
|
||||
<CheckBox x:Uid="FancyZones_ShowZonesOnAllMonitorsCheckBoxControl" IsChecked="{x:Bind ViewModel.ShowOnAllMonitors, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
<tkcontrols:SettingsCard ContentAlignment="Left">
|
||||
<tkcontrols:SettingsCard Name="FancyZonesSpanZonesAcrossMonitors" ContentAlignment="Left">
|
||||
<controls:CheckBoxWithDescriptionControl x:Uid="FancyZones_SpanZonesAcrossMonitors" IsChecked="{x:Bind ViewModel.SpanZonesAcrossMonitors, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
<tkcontrols:SettingsCard Name="FancyZonesOverlappingZones" x:Uid="FancyZones_OverlappingZones">
|
||||
@@ -106,7 +110,7 @@
|
||||
<ComboBoxItem x:Uid="FancyZones_Radio_Default_Theme" />
|
||||
</ComboBox>
|
||||
<tkcontrols:SettingsExpander.Items>
|
||||
<tkcontrols:SettingsCard ContentAlignment="Left">
|
||||
<tkcontrols:SettingsCard Name="FancyZonesPreviewCard" ContentAlignment="Left">
|
||||
<controls:FancyZonesPreviewControl
|
||||
Width="192"
|
||||
Height="108"
|
||||
@@ -118,7 +122,7 @@
|
||||
IsSystemTheme="{x:Bind ViewModel.SystemTheme, Mode=OneWay}"
|
||||
ShowZoneNumber="{x:Bind Path=ViewModel.ShowZoneNumber, Mode=OneWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
<tkcontrols:SettingsCard ContentAlignment="Left">
|
||||
<tkcontrols:SettingsCard Name="FancyZonesShowZoneNumberCheckBoxControl" ContentAlignment="Left">
|
||||
<CheckBox x:Uid="FancyZones_ShowZoneNumberCheckBoxControl" IsChecked="{x:Bind ViewModel.ShowZoneNumber, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
<tkcontrols:SettingsCard Name="FancyZonesHighlightOpacity" x:Uid="FancyZones_HighlightOpacity">
|
||||
@@ -164,28 +168,31 @@
|
||||
x:Uid="FancyZones_WindowBehavior_GroupSettings"
|
||||
IsExpanded="True">
|
||||
<tkcontrols:SettingsExpander.Items>
|
||||
<tkcontrols:SettingsCard ContentAlignment="Left">
|
||||
<tkcontrols:SettingsCard Name="FancyZonesDisplayOrWorkAreaChangeMoveWindowsCheckBoxControl" ContentAlignment="Left">
|
||||
<CheckBox x:Uid="FancyZones_DisplayOrWorkAreaChangeMoveWindowsCheckBoxControl" IsChecked="{x:Bind ViewModel.DisplayOrWorkAreaChangeMoveWindows, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
<tkcontrols:SettingsCard ContentAlignment="Left">
|
||||
<tkcontrols:SettingsCard Name="FancyZonesZoneSetChangeMoveWindows" ContentAlignment="Left">
|
||||
<CheckBox x:Uid="FancyZones_ZoneSetChangeMoveWindows" IsChecked="{x:Bind ViewModel.ZoneSetChangeMoveWindows, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
<tkcontrols:SettingsCard ContentAlignment="Left">
|
||||
<tkcontrols:SettingsCard Name="FancyZonesAppLastZoneMoveWindows" ContentAlignment="Left">
|
||||
<CheckBox x:Uid="FancyZones_AppLastZoneMoveWindows" IsChecked="{x:Bind ViewModel.AppLastZoneMoveWindows, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
<tkcontrols:SettingsCard ContentAlignment="Left">
|
||||
<tkcontrols:SettingsCard Name="FancyZonesOpenWindowOnActiveMonitor" ContentAlignment="Left">
|
||||
<CheckBox x:Uid="FancyZones_OpenWindowOnActiveMonitor" IsChecked="{x:Bind ViewModel.OpenWindowOnActiveMonitor, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
<tkcontrols:SettingsCard ContentAlignment="Left">
|
||||
<tkcontrols:SettingsCard Name="FancyZonesRestoreSize" ContentAlignment="Left">
|
||||
<CheckBox x:Uid="FancyZones_RestoreSize" IsChecked="{x:Bind ViewModel.RestoreSize, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
<tkcontrols:SettingsCard ContentAlignment="Left">
|
||||
<tkcontrols:SettingsCard Name="FancyZonesMakeDraggedWindowTransparentCheckBoxControl" ContentAlignment="Left">
|
||||
<CheckBox x:Uid="FancyZones_MakeDraggedWindowTransparentCheckBoxControl" IsChecked="{x:Bind ViewModel.MakeDraggedWindowsTransparent, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
<tkcontrols:SettingsCard ContentAlignment="Left">
|
||||
<tkcontrols:SettingsCard Name="FancyZonesAllowChildWindowSnap" ContentAlignment="Left">
|
||||
<CheckBox x:Uid="FancyZones_AllowChildWindowSnap" IsChecked="{x:Bind ViewModel.AllowChildWindowSnap, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
<tkcontrols:SettingsCard ContentAlignment="Left" Visibility="{x:Bind ViewModel.Windows11, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
<tkcontrols:SettingsCard
|
||||
Name="FancyZonesDisableRoundCornersOnWindowSnap"
|
||||
ContentAlignment="Left"
|
||||
Visibility="{x:Bind ViewModel.Windows11, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
<CheckBox x:Uid="FancyZones_DisableRoundCornersOnWindowSnap" IsChecked="{x:Bind ViewModel.DisableRoundCornersOnWindowSnap, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
</tkcontrols:SettingsExpander.Items>
|
||||
@@ -199,7 +206,7 @@
|
||||
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.WindowSwitching, Mode=TwoWay}" />
|
||||
<tkcontrols:SettingsExpander.Items>
|
||||
<!-- HACK: For some weird reason, a Shortcut Control is not working correctly if it's the first item in the expander, so we add an invisible card as the first one. -->
|
||||
<tkcontrols:SettingsCard Visibility="Collapsed" />
|
||||
<tkcontrols:SettingsCard Name="FancyZonesWindowSwitchingPlaceholder" Visibility="Collapsed" />
|
||||
<tkcontrols:SettingsCard
|
||||
Name="FancyZonesHotkeyNextTabControl"
|
||||
x:Uid="FancyZones_HotkeyNextTabControl"
|
||||
@@ -248,7 +255,10 @@
|
||||
</ComboBoxItem>
|
||||
</ComboBox>
|
||||
</tkcontrols:SettingsCard>
|
||||
<tkcontrols:SettingsCard ContentAlignment="Left" IsEnabled="{x:Bind ViewModel.SnapHotkeysCategoryEnabled, Mode=OneWay}">
|
||||
<tkcontrols:SettingsCard
|
||||
Name="FancyZonesMoveWindowsAcrossAllMonitorsCheckBoxControl"
|
||||
ContentAlignment="Left"
|
||||
IsEnabled="{x:Bind ViewModel.SnapHotkeysCategoryEnabled, Mode=OneWay}">
|
||||
<CheckBox x:Uid="FancyZones_MoveWindowsAcrossAllMonitorsCheckBoxControl" IsChecked="{x:Bind ViewModel.MoveWindowsAcrossMonitors, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
</tkcontrols:SettingsExpander.Items>
|
||||
@@ -262,7 +272,10 @@
|
||||
HeaderIcon="{ui:FontIcon Glyph=}">
|
||||
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.QuickLayoutSwitch, Mode=TwoWay}" />
|
||||
<tkcontrols:SettingsExpander.Items>
|
||||
<tkcontrols:SettingsCard ContentAlignment="Left" IsEnabled="{x:Bind ViewModel.QuickSwitchEnabled, Mode=OneWay}">
|
||||
<tkcontrols:SettingsCard
|
||||
Name="FancyZonesFlashZonesOnQuickSwitch"
|
||||
ContentAlignment="Left"
|
||||
IsEnabled="{x:Bind ViewModel.QuickSwitchEnabled, Mode=OneWay}">
|
||||
<CheckBox x:Uid="FancyZones_FlashZonesOnQuickSwitch" IsChecked="{x:Bind ViewModel.FlashZonesOnQuickSwitch, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
</tkcontrols:SettingsExpander.Items>
|
||||
@@ -277,7 +290,10 @@
|
||||
HeaderIcon="{ui:FontIcon Glyph=}"
|
||||
IsExpanded="True">
|
||||
<tkcontrols:SettingsExpander.Items>
|
||||
<tkcontrols:SettingsCard HorizontalContentAlignment="Stretch" ContentAlignment="Vertical">
|
||||
<tkcontrols:SettingsCard
|
||||
Name="FancyZonesExcludeAppsTextBoxControl"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
ContentAlignment="Vertical">
|
||||
<TextBox
|
||||
x:Uid="FancyZones_ExcludeApps_TextBoxControl"
|
||||
MinWidth="240"
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using CommunityToolkit.WinUI.Controls;
|
||||
using Microsoft.PowerToys.Settings.UI.Helpers;
|
||||
using Microsoft.PowerToys.Settings.UI.Services;
|
||||
using Microsoft.PowerToys.Settings.UI.ViewModels;
|
||||
@@ -32,7 +33,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
if (e.Parameter is SearchResultsNavigationParams searchParams)
|
||||
{
|
||||
ViewModel.SetSearchResults(searchParams.Query, searchParams.Results);
|
||||
PageControl.ModuleDescription = string.Empty;
|
||||
PageControl.ModuleDescription = $"{ResourceLoaderInstance.ResourceLoader.GetString("Search_ResultsFor")} '{searchParams.Query}'";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +44,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
|
||||
private void ModuleButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is CommunityToolkit.WinUI.Controls.SettingsCard card && card.DataContext is SettingEntry tagEntry)
|
||||
if (sender is SettingsCard card && card.DataContext is SettingEntry tagEntry)
|
||||
{
|
||||
NavigateToModule(tagEntry);
|
||||
}
|
||||
@@ -51,7 +52,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
|
||||
private void SettingButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is CommunityToolkit.WinUI.Controls.SettingsCard card && card.DataContext is SettingEntry tagEntry)
|
||||
if (sender is SettingsCard card && card.DataContext is SettingEntry tagEntry)
|
||||
{
|
||||
NavigateToSetting(tagEntry);
|
||||
}
|
||||
|
||||
@@ -57,12 +57,6 @@
|
||||
</DataTemplate>
|
||||
<DataTemplate x:Key="NoResultSearchResultTemplate" x:DataType="models:SuggestionItem">
|
||||
<Grid>
|
||||
<Rectangle
|
||||
Height="1"
|
||||
Margin="0,-4,0,0"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Top"
|
||||
Fill="{ThemeResource DividerStrokeColorDefaultBrush}" />
|
||||
<TextBlock
|
||||
Margin="8"
|
||||
HorizontalAlignment="Center"
|
||||
@@ -71,10 +65,10 @@
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
<DataTemplate x:Key="ShowAllSearchResultTemplate" x:DataType="models:SuggestionItem">
|
||||
<Grid Padding="16,8">
|
||||
<Grid>
|
||||
<Rectangle
|
||||
Height="1"
|
||||
Margin="0,-4,0,0"
|
||||
Margin="0,-12,0,0"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Top"
|
||||
Fill="{ThemeResource DividerStrokeColorDefaultBrush}" />
|
||||
@@ -92,7 +86,6 @@
|
||||
<ic:InvokeCommandAction Command="{x:Bind ViewModel.LoadedCommand}" />
|
||||
</ic:EventTriggerBehavior>
|
||||
</i:Interaction.Behaviors>
|
||||
|
||||
<Grid x:Name="RootGrid">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="48" />
|
||||
@@ -153,11 +146,17 @@
|
||||
</NavigationView.Resources>
|
||||
<NavigationView.MenuItems>
|
||||
<NavigationViewItem
|
||||
x:Name="DashboardNavigationItem"
|
||||
x:Uid="Shell_Dashboard"
|
||||
helpers:NavHelper.NavigateTo="views:DashboardPage"
|
||||
AutomationProperties.AutomationId="DashboardNavItem"
|
||||
Icon="{ui:FontIcon Glyph=}" />
|
||||
|
||||
<NavigationViewItem x:Uid="Shell_General" helpers:NavHelper.NavigateTo="views:GeneralPage">
|
||||
<NavigationViewItem
|
||||
x:Name="GeneralNavigationItem"
|
||||
x:Uid="Shell_General"
|
||||
helpers:NavHelper.NavigateTo="views:GeneralPage"
|
||||
AutomationProperties.AutomationId="GeneralNavItem">
|
||||
<NavigationViewItem.Icon>
|
||||
<AnimatedIcon>
|
||||
<AnimatedIcon.Source>
|
||||
@@ -173,156 +172,220 @@
|
||||
|
||||
<!-- System Tools -->
|
||||
<NavigationViewItem
|
||||
x:Name="SystemToolsNavigationItem"
|
||||
x:Uid="Shell_TopLevelSystemTools"
|
||||
AutomationProperties.AutomationId="SystemToolsNavItem"
|
||||
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/SystemTools.png}"
|
||||
SelectsOnInvoked="False">
|
||||
<NavigationViewItem.MenuItems>
|
||||
<NavigationViewItem
|
||||
x:Name="AdvancedPasteNavigationItem"
|
||||
x:Uid="Shell_AdvancedPaste"
|
||||
helpers:NavHelper.NavigateTo="views:AdvancedPastePage"
|
||||
AutomationProperties.AutomationId="AdvancedPasteNavItem"
|
||||
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/AdvancedPaste.png}" />
|
||||
<NavigationViewItem
|
||||
x:Name="AwakeNavigationItem"
|
||||
x:Uid="Shell_Awake"
|
||||
helpers:NavHelper.NavigateTo="views:AwakePage"
|
||||
AutomationProperties.AutomationId="AwakeNavItem"
|
||||
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/Awake.png}" />
|
||||
<NavigationViewItem
|
||||
x:Name="CmdPalNavigationItem"
|
||||
x:Uid="Shell_CmdPal"
|
||||
helpers:NavHelper.NavigateTo="views:CmdPalPage"
|
||||
AutomationProperties.AutomationId="CmdPalNavItem"
|
||||
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/CmdPal.png}" />
|
||||
<NavigationViewItem
|
||||
x:Name="ColorPickerNavigationItem"
|
||||
x:Uid="Shell_ColorPicker"
|
||||
helpers:NavHelper.NavigateTo="views:ColorPickerPage"
|
||||
AutomationProperties.AutomationId="ColorPickerNavItem"
|
||||
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/ColorPicker.png}" />
|
||||
<NavigationViewItem
|
||||
x:Name="PowerLauncherNavigationItem"
|
||||
x:Uid="Shell_PowerLauncher"
|
||||
helpers:NavHelper.NavigateTo="views:PowerLauncherPage"
|
||||
AutomationProperties.AutomationId="PowerLauncherNavItem"
|
||||
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/PowerToysRun.png}" />
|
||||
<NavigationViewItem
|
||||
x:Name="MeasureToolNavigationItem"
|
||||
x:Uid="Shell_MeasureTool"
|
||||
helpers:NavHelper.NavigateTo="views:MeasureToolPage"
|
||||
AutomationProperties.AutomationId="MeasureToolNavItem"
|
||||
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/ScreenRuler.png}" />
|
||||
<NavigationViewItem
|
||||
x:Name="ShortcutGuideNavigationItem"
|
||||
x:Uid="Shell_ShortcutGuide"
|
||||
helpers:NavHelper.NavigateTo="views:ShortcutGuidePage"
|
||||
AutomationProperties.AutomationId="ShortcutGuideNavItem"
|
||||
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/ShortcutGuide.png}" />
|
||||
<NavigationViewItem
|
||||
x:Name="TextExtractorNavigationItem"
|
||||
x:Uid="Shell_TextExtractor"
|
||||
helpers:NavHelper.NavigateTo="views:PowerOcrPage"
|
||||
AutomationProperties.AutomationId="TextExtractorNavItem"
|
||||
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/TextExtractor.png}" />
|
||||
<NavigationViewItem
|
||||
x:Name="ZoomItNavigationItem"
|
||||
x:Uid="Shell_ZoomIt"
|
||||
helpers:NavHelper.NavigateTo="views:ZoomItPage"
|
||||
AutomationProperties.AutomationId="ZoomItNavItem"
|
||||
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/ZoomIt.png}" />
|
||||
</NavigationViewItem.MenuItems>
|
||||
</NavigationViewItem>
|
||||
|
||||
<!-- Windowing & Layouts -->
|
||||
<NavigationViewItem
|
||||
x:Name="WindowingAndLayoutsNavigationItem"
|
||||
x:Uid="Shell_TopLevelWindowsAndLayouts "
|
||||
AutomationProperties.AutomationId="WindowingAndLayoutsNavItem"
|
||||
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/WindowingAndLayouts.png}"
|
||||
SelectsOnInvoked="False">
|
||||
<NavigationViewItem.MenuItems>
|
||||
<NavigationViewItem
|
||||
x:Name="AlwaysOnTopNavigationItem"
|
||||
x:Uid="Shell_AlwaysOnTop"
|
||||
helpers:NavHelper.NavigateTo="views:AlwaysOnTopPage"
|
||||
AutomationProperties.AutomationId="AlwaysOnTopNavItem"
|
||||
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/AlwaysOnTop.png}" />
|
||||
<NavigationViewItem
|
||||
x:Name="CropAndLockNavigationItem"
|
||||
x:Uid="Shell_CropAndLock"
|
||||
helpers:NavHelper.NavigateTo="views:CropAndLockPage"
|
||||
AutomationProperties.AutomationId="CropAndLockNavItem"
|
||||
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/CropAndLock.png}" />
|
||||
<NavigationViewItem
|
||||
x:Name="FancyZonesNavigationItem"
|
||||
x:Uid="Shell_FancyZones"
|
||||
helpers:NavHelper.NavigateTo="views:FancyZonesPage"
|
||||
AutomationProperties.AutomationId="FancyZonesNavItem"
|
||||
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/FancyZones.png}" />
|
||||
<NavigationViewItem
|
||||
x:Name="WorkspacesNavigationItem"
|
||||
x:Uid="Shell_Workspaces"
|
||||
helpers:NavHelper.NavigateTo="views:WorkspacesPage"
|
||||
AutomationProperties.AutomationId="WorkspacesNavItem"
|
||||
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/Workspaces.png}" />
|
||||
</NavigationViewItem.MenuItems>
|
||||
</NavigationViewItem>
|
||||
|
||||
<!-- Input / Output -->
|
||||
<NavigationViewItem
|
||||
x:Name="InputOutputNavigationItem"
|
||||
x:Uid="Shell_TopLevelInputOutput"
|
||||
AutomationProperties.AutomationId="InputOutputNavItem"
|
||||
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/InputOutput.png}"
|
||||
SelectsOnInvoked="False">
|
||||
<NavigationViewItem.MenuItems>
|
||||
<NavigationViewItem
|
||||
x:Name="KeyboardManagerNavigationItem"
|
||||
x:Uid="Shell_KeyboardManager"
|
||||
helpers:NavHelper.NavigateTo="views:KeyboardManagerPage"
|
||||
AutomationProperties.AutomationId="KeyboardManagerNavItem"
|
||||
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/KeyboardManager.png}" />
|
||||
<!-- Find my mouse -->
|
||||
<!-- Mouse Highlighter -->
|
||||
<NavigationViewItem
|
||||
x:Name="MouseUtilitiesNavigationItem"
|
||||
x:Uid="Shell_MouseUtilities"
|
||||
helpers:NavHelper.NavigateTo="views:MouseUtilsPage"
|
||||
AutomationProperties.AutomationId="MouseUtilitiesNavItem"
|
||||
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/MouseUtils.png}" />
|
||||
<NavigationViewItem
|
||||
x:Name="MouseWithoutBordersNavigationItem"
|
||||
x:Uid="Shell_MouseWithoutBorders"
|
||||
helpers:NavHelper.NavigateTo="views:MouseWithoutBordersPage"
|
||||
AutomationProperties.AutomationId="MouseWithoutBordersNavItem"
|
||||
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/MouseWithoutBorders.png}" />
|
||||
<NavigationViewItem
|
||||
x:Name="QuickAccentNavigationItem"
|
||||
x:Uid="Shell_QuickAccent"
|
||||
helpers:NavHelper.NavigateTo="views:PowerAccentPage"
|
||||
AutomationProperties.AutomationId="QuickAccentNavItem"
|
||||
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/QuickAccent.png}" />
|
||||
</NavigationViewItem.MenuItems>
|
||||
</NavigationViewItem>
|
||||
|
||||
<!-- File Management -->
|
||||
<NavigationViewItem
|
||||
x:Name="FileManagementNavigationItem"
|
||||
x:Uid="Shell_TopLevelFileManagement"
|
||||
AutomationProperties.AutomationId="FileManagementNavItem"
|
||||
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/FileManagement.png}"
|
||||
SelectsOnInvoked="False">
|
||||
<NavigationViewItem.MenuItems>
|
||||
<NavigationViewItem
|
||||
x:Name="PowerPreviewNavigationItem"
|
||||
x:Uid="Shell_PowerPreview"
|
||||
helpers:NavHelper.NavigateTo="views:PowerPreviewPage"
|
||||
AutomationProperties.AutomationId="PowerPreviewNavItem"
|
||||
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/FileExplorerPreview.png}" />
|
||||
<!-- File Explorer Thumbnails -->
|
||||
<NavigationViewItem
|
||||
x:Name="FileLocksmithNavigationItem"
|
||||
x:Uid="Shell_FileLocksmith"
|
||||
helpers:NavHelper.NavigateTo="views:FileLocksmithPage"
|
||||
AutomationProperties.AutomationId="FileLocksmithNavItem"
|
||||
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/FileLocksmith.png}" />
|
||||
<NavigationViewItem
|
||||
x:Name="ImageResizerNavigationItem"
|
||||
x:Uid="Shell_ImageResizer"
|
||||
helpers:NavHelper.NavigateTo="views:ImageResizerPage"
|
||||
AutomationProperties.AutomationId="ImageResizerNavItem"
|
||||
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/ImageResizer.png}" />
|
||||
<NavigationViewItem
|
||||
x:Name="NewPlusNavigationItem"
|
||||
x:Uid="NewPlus_Product_Name"
|
||||
helpers:NavHelper.NavigateTo="views:NewPlusPage"
|
||||
AutomationProperties.AutomationId="NewPlusNavItem"
|
||||
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/NewPlus.png}" />
|
||||
<NavigationViewItem
|
||||
x:Name="PeekNavigationItem"
|
||||
x:Uid="Shell_Peek"
|
||||
helpers:NavHelper.NavigateTo="views:PeekPage"
|
||||
AutomationProperties.AutomationId="PeekNavItem"
|
||||
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/Peek.png}" />
|
||||
<NavigationViewItem
|
||||
x:Name="PowerRenameNavigationItem"
|
||||
x:Uid="Shell_PowerRename"
|
||||
helpers:NavHelper.NavigateTo="views:PowerRenamePage"
|
||||
AutomationProperties.AutomationId="PowerRenameNavItem"
|
||||
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/PowerRename.png}" />
|
||||
</NavigationViewItem.MenuItems>
|
||||
</NavigationViewItem>
|
||||
|
||||
<!-- Advanced -->
|
||||
<NavigationViewItem
|
||||
x:Name="AdvancedNavigationItem"
|
||||
x:Uid="Shell_TopLevelAdvanced"
|
||||
AutomationProperties.AutomationId="AdvancedNavItem"
|
||||
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/Advanced.png}"
|
||||
SelectsOnInvoked="False">
|
||||
<NavigationViewItem.MenuItems>
|
||||
<NavigationViewItem
|
||||
x:Name="CmdNotFoundNavigationItem"
|
||||
x:Uid="Shell_CmdNotFound"
|
||||
helpers:NavHelper.NavigateTo="views:CmdNotFoundPage"
|
||||
AutomationProperties.AutomationId="CmdNotFoundNavItem"
|
||||
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/CommandNotFound.png}" />
|
||||
<NavigationViewItem
|
||||
x:Name="EnvironmentVariablesNavigationItem"
|
||||
x:Uid="Shell_EnvironmentVariables"
|
||||
helpers:NavHelper.NavigateTo="views:EnvironmentVariablesPage"
|
||||
AutomationProperties.AutomationId="EnvironmentVariablesNavItem"
|
||||
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/EnvironmentVariables.png}" />
|
||||
<NavigationViewItem
|
||||
x:Name="HostsNavigationItem"
|
||||
x:Uid="Shell_Hosts"
|
||||
helpers:NavHelper.NavigateTo="views:HostsPage"
|
||||
AutomationProperties.AutomationId="HostsNavItem"
|
||||
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/Hosts.png}" />
|
||||
<NavigationViewItem
|
||||
x:Name="RegistryPreviewNavigationItem"
|
||||
x:Uid="Shell_RegistryPreview"
|
||||
helpers:NavHelper.NavigateTo="views:RegistryPreviewPage"
|
||||
AutomationProperties.AutomationId="RegistryPreviewNavItem"
|
||||
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/RegistryPreview.png}" />
|
||||
</NavigationViewItem.MenuItems>
|
||||
</NavigationViewItem>
|
||||
@@ -330,19 +393,27 @@
|
||||
<NavigationView.PaneFooter>
|
||||
<StackPanel Orientation="Vertical">
|
||||
<NavigationViewItem
|
||||
x:Name="OOBENavigationItem"
|
||||
x:Uid="OOBE_NavViewItem"
|
||||
AutomationProperties.AutomationId="OOBENavItem"
|
||||
Icon="{ui:FontIcon Glyph=}"
|
||||
Tapped="OOBEItem_Tapped" />
|
||||
<NavigationViewItem
|
||||
x:Name="WhatIsNewNavigationItem"
|
||||
x:Uid="WhatIsNew_NavViewItem"
|
||||
AutomationProperties.AutomationId="WhatIsNewNavItem"
|
||||
Icon="{ui:FontIcon Glyph=}"
|
||||
Tapped="WhatIsNewItem_Tapped" />
|
||||
<NavigationViewItem
|
||||
x:Name="FeedbackNavigationItem"
|
||||
x:Uid="Feedback_NavViewItem"
|
||||
AutomationProperties.AutomationId="FeedbackNavItem"
|
||||
Icon="{ui:FontIcon Glyph=}"
|
||||
Tapped="FeedbackItem_Tapped" />
|
||||
<NavigationViewItem
|
||||
x:Name="CloseNavigationItem"
|
||||
x:Uid="Close_NavViewItem"
|
||||
AutomationProperties.AutomationId="CloseNavItem"
|
||||
Icon="{ui:FontIcon Glyph=}"
|
||||
Tapped="Close_Tapped"
|
||||
Visibility="{x:Bind ViewModel.ShowCloseMenu, Mode=OneWay}" />
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -142,8 +141,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
private const int SearchDebounceMs = 500;
|
||||
private bool _disposed;
|
||||
|
||||
// Tracing id for correlating logs of a single search interaction
|
||||
private static long _searchTraceIdCounter;
|
||||
// Removed trace id counter per cleanup
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ShellPage"/> class.
|
||||
@@ -443,25 +441,11 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
|
||||
private void ShellPage_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Logger.LogDebug("[Search][Index] Scheduling BuildIndex...");
|
||||
var swIndex = Stopwatch.StartNew();
|
||||
Task.Run(() =>
|
||||
{
|
||||
Logger.LogDebug("[Search][Index] BuildIndex started");
|
||||
SearchIndexService.BuildIndex();
|
||||
})
|
||||
.ContinueWith(t =>
|
||||
{
|
||||
swIndex.Stop();
|
||||
if (t.IsFaulted)
|
||||
{
|
||||
Logger.LogDebug($"[Search][Index] BuildIndex FAILED after {swIndex.ElapsedMilliseconds} ms: {t.Exception?.Flatten().InnerException?.Message}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.LogDebug($"[Search][Index] BuildIndex completed in {swIndex.ElapsedMilliseconds} ms.");
|
||||
}
|
||||
});
|
||||
.ContinueWith(_ => { });
|
||||
}
|
||||
|
||||
private void NavigationView_DisplayModeChanged(NavigationView sender, NavigationViewDisplayModeChangedEventArgs args)
|
||||
@@ -512,10 +496,6 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
|
||||
var query = sender.Text?.Trim() ?? string.Empty;
|
||||
|
||||
var traceId = Interlocked.Increment(ref _searchTraceIdCounter);
|
||||
var swOverall = Stopwatch.StartNew();
|
||||
Logger.LogDebug($"[Search][TextChanged][{traceId}] start. query='{query}'");
|
||||
|
||||
// Debounce: cancel previous pending search
|
||||
_searchDebounceCts?.Cancel();
|
||||
_searchDebounceCts?.Dispose();
|
||||
@@ -528,7 +508,6 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
sender.IsSuggestionListOpen = false;
|
||||
_lastSearchResults.Clear();
|
||||
_lastQueryText = string.Empty;
|
||||
Logger.LogDebug($"[Search][TextChanged][{traceId}] empty query. end");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -538,14 +517,11 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
// A newer keystroke arrived; abandon this run
|
||||
Logger.LogDebug($"[Search][TextChanged][{traceId}] debounce canceled at +{swOverall.ElapsedMilliseconds} ms");
|
||||
return;
|
||||
return; // debounce canceled
|
||||
}
|
||||
|
||||
if (token.IsCancellationRequested)
|
||||
{
|
||||
Logger.LogDebug($"[Search][TextChanged][{traceId}] token canceled post-debounce at +{swOverall.ElapsedMilliseconds} ms");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -554,106 +530,25 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
try
|
||||
{
|
||||
// If the token is already canceled before scheduling, the task won't start.
|
||||
var swSearch = Stopwatch.StartNew();
|
||||
Logger.LogDebug($"[Search][TextChanged][{traceId}] dispatch search...");
|
||||
results = await Task.Run(() => SearchIndexService.Search(query, token), token);
|
||||
swSearch.Stop();
|
||||
Logger.LogDebug($"[Search][TextChanged][{traceId}] search done in {swSearch.ElapsedMilliseconds} ms. results={results?.Count ?? 0}");
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
Logger.LogDebug($"[Search][TextChanged][{traceId}] search canceled at +{swOverall.ElapsedMilliseconds} ms");
|
||||
return;
|
||||
}
|
||||
|
||||
if (token.IsCancellationRequested)
|
||||
{
|
||||
Logger.LogDebug($"[Search][TextChanged][{traceId}] token canceled after search at +{swOverall.ElapsedMilliseconds} ms");
|
||||
return;
|
||||
}
|
||||
|
||||
_lastSearchResults = results;
|
||||
_lastQueryText = query;
|
||||
|
||||
List<SuggestionItem> top;
|
||||
if (results.Count == 0)
|
||||
{
|
||||
// Explicit no-results row
|
||||
var rl = ResourceLoaderInstance.ResourceLoader;
|
||||
var noResultsPrefix = rl.GetString("Shell_Search_NoResults");
|
||||
if (string.IsNullOrEmpty(noResultsPrefix))
|
||||
{
|
||||
noResultsPrefix = "No results for";
|
||||
}
|
||||
var top = BuildSuggestionItems(query, results);
|
||||
|
||||
var headerText = $"{noResultsPrefix} '{query}'";
|
||||
top =
|
||||
[
|
||||
new()
|
||||
{
|
||||
Header = headerText,
|
||||
IsNoResults = true,
|
||||
},
|
||||
];
|
||||
|
||||
Logger.LogDebug($"[Search][TextChanged][{traceId}] no results -> added placeholder item (count={top.Count})");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Project top 5 suggestions
|
||||
var swProject = Stopwatch.StartNew();
|
||||
top = [.. results.Take(5)
|
||||
.Select(e =>
|
||||
{
|
||||
string subtitle = string.Empty;
|
||||
if (e.Type != EntryType.SettingsPage)
|
||||
{
|
||||
var swSubtitle = Stopwatch.StartNew();
|
||||
subtitle = SearchIndexService.GetLocalizedPageName(e.PageTypeName);
|
||||
if (string.IsNullOrEmpty(subtitle))
|
||||
{
|
||||
// Fallback: look up the module title from the in-memory index
|
||||
var swFallback = Stopwatch.StartNew();
|
||||
subtitle = SearchIndexService.Index
|
||||
.Where(x => x.Type == EntryType.SettingsPage && x.PageTypeName == e.PageTypeName)
|
||||
.Select(x => x.Header)
|
||||
.FirstOrDefault() ?? string.Empty;
|
||||
swFallback.Stop();
|
||||
Logger.LogDebug($"[Search][TextChanged][{traceId}] fallback subtitle for '{e.PageTypeName}' took {swFallback.ElapsedMilliseconds} ms");
|
||||
}
|
||||
|
||||
swSubtitle.Stop();
|
||||
Logger.LogDebug($"[Search][TextChanged][{traceId}] subtitle for '{e.PageTypeName}' took {swSubtitle.ElapsedMilliseconds} ms");
|
||||
}
|
||||
|
||||
return new SuggestionItem
|
||||
{
|
||||
Header = e.Header,
|
||||
Icon = e.Icon,
|
||||
PageTypeName = e.PageTypeName,
|
||||
ElementName = e.ElementName,
|
||||
ParentElementName = e.ParentElementName,
|
||||
Subtitle = subtitle,
|
||||
IsShowAll = false,
|
||||
};
|
||||
})];
|
||||
swProject.Stop();
|
||||
Logger.LogDebug($"[Search][TextChanged][{traceId}] project suggestions took {swProject.ElapsedMilliseconds} ms. topCount={top.Count}");
|
||||
|
||||
if (results.Count > 5)
|
||||
{
|
||||
// Add a tail item to show all results if there are more than 5
|
||||
top.Add(new SuggestionItem { IsShowAll = true });
|
||||
Logger.LogDebug($"[Search][TextChanged][{traceId}] added 'Show all results' item");
|
||||
}
|
||||
}
|
||||
|
||||
var swUi = Stopwatch.StartNew();
|
||||
sender.ItemsSource = top;
|
||||
sender.IsSuggestionListOpen = top.Count > 0;
|
||||
swUi.Stop();
|
||||
swOverall.Stop();
|
||||
Logger.LogDebug($"[Search][TextChanged][{traceId}] UI update took {swUi.ElapsedMilliseconds} ms. total={swOverall.ElapsedMilliseconds} ms");
|
||||
}
|
||||
|
||||
private void SearchBox_SuggestionChosen(AutoSuggestBox sender, AutoSuggestBoxSuggestionChosenEventArgs args)
|
||||
@@ -710,23 +605,98 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
private void CtrlF_Invoked(KeyboardAccelerator sender, KeyboardAcceleratorInvokedEventArgs args)
|
||||
{
|
||||
SearchBox.Focus(FocusState.Programmatic);
|
||||
args.Handled = true; // prevent further processing (e.g., unintended navigation)
|
||||
}
|
||||
|
||||
private void SearchBox_GotFocus(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// do not prompt unless search for text.
|
||||
return;
|
||||
var box = sender as AutoSuggestBox;
|
||||
var current = box?.Text?.Trim() ?? string.Empty;
|
||||
if (string.IsNullOrEmpty(current))
|
||||
{
|
||||
return; // nothing to restore
|
||||
}
|
||||
|
||||
// If current text matches last query and we have results, reconstruct the suggestion list.
|
||||
if (string.Equals(current, _lastQueryText, StringComparison.Ordinal) && _lastSearchResults?.Count > 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
var top = BuildSuggestionItems(current, _lastSearchResults);
|
||||
box.ItemsSource = top;
|
||||
box.IsSuggestionListOpen = top.Count > 0;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError($"Error restoring suggestion list {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Centralized suggestion projection logic used by TextChanged & GotFocus restore.
|
||||
private List<SuggestionItem> BuildSuggestionItems(string query, List<SettingEntry> results)
|
||||
{
|
||||
results ??= new();
|
||||
if (results.Count == 0)
|
||||
{
|
||||
var rl = ResourceLoaderInstance.ResourceLoader;
|
||||
var noResultsPrefix = rl.GetString("Shell_Search_NoResults");
|
||||
if (string.IsNullOrEmpty(noResultsPrefix))
|
||||
{
|
||||
noResultsPrefix = "No results for";
|
||||
}
|
||||
|
||||
var headerText = $"{noResultsPrefix} '{query}'";
|
||||
return new List<SuggestionItem>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Header = headerText,
|
||||
IsNoResults = true,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
var list = results.Take(5).Select(e =>
|
||||
{
|
||||
string subtitle = string.Empty;
|
||||
if (e.Type != EntryType.SettingsPage)
|
||||
{
|
||||
subtitle = SearchIndexService.GetLocalizedPageName(e.PageTypeName);
|
||||
if (string.IsNullOrEmpty(subtitle))
|
||||
{
|
||||
subtitle = SearchIndexService.Index
|
||||
.Where(x => x.Type == EntryType.SettingsPage && x.PageTypeName == e.PageTypeName)
|
||||
.Select(x => x.Header)
|
||||
.FirstOrDefault() ?? string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
return new SuggestionItem
|
||||
{
|
||||
Header = e.Header,
|
||||
Icon = e.Icon,
|
||||
PageTypeName = e.PageTypeName,
|
||||
ElementName = e.ElementName,
|
||||
ParentElementName = e.ParentElementName,
|
||||
Subtitle = subtitle,
|
||||
IsShowAll = false,
|
||||
};
|
||||
}).ToList();
|
||||
|
||||
if (results.Count > 5)
|
||||
{
|
||||
list.Add(new SuggestionItem { IsShowAll = true });
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
private async void SearchBox_QuerySubmitted(AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args)
|
||||
{
|
||||
var swSubmit = Stopwatch.StartNew();
|
||||
Logger.LogDebug("[Search][Submit] start");
|
||||
|
||||
// If a suggestion is selected, navigate directly
|
||||
if (args.ChosenSuggestion is SuggestionItem chosen)
|
||||
{
|
||||
Logger.LogDebug($"[Search][Submit] chosen suggestion -> navigate to {chosen.PageTypeName} element={chosen.ElementName ?? "<page>"}");
|
||||
NavigateFromSuggestion(chosen);
|
||||
return;
|
||||
}
|
||||
@@ -734,7 +704,6 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
var queryText = (args.QueryText ?? _lastQueryText)?.Trim();
|
||||
if (string.IsNullOrWhiteSpace(queryText))
|
||||
{
|
||||
Logger.LogDebug("[Search][Submit] empty query -> navigate Dashboard");
|
||||
NavigationService.Navigate<DashboardPage>();
|
||||
return;
|
||||
}
|
||||
@@ -742,21 +711,10 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
// Prefer cached results (from live search); if empty, perform a fresh search
|
||||
var matched = _lastSearchResults?.Count > 0 && string.Equals(_lastQueryText, queryText, StringComparison.Ordinal)
|
||||
? _lastSearchResults
|
||||
: await Task.Run(() =>
|
||||
{
|
||||
var sw = Stopwatch.StartNew();
|
||||
Logger.LogDebug($"[Search][Submit] background search for '{queryText}'...");
|
||||
var r = SearchIndexService.Search(queryText);
|
||||
sw.Stop();
|
||||
Logger.LogDebug($"[Search][Submit] background search done in {sw.ElapsedMilliseconds} ms. results={r?.Count ?? 0}");
|
||||
return r;
|
||||
});
|
||||
: await Task.Run(() => SearchIndexService.Search(queryText));
|
||||
|
||||
var searchParams = new SearchResultsNavigationParams(queryText, matched);
|
||||
Logger.LogDebug($"[Search][Submit] navigate to SearchResultsPage (results={matched?.Count ?? 0})");
|
||||
NavigationService.Navigate<SearchResultsPage>(searchParams);
|
||||
swSubmit.Stop();
|
||||
Logger.LogDebug($"[Search][Submit] total {swSubmit.ElapsedMilliseconds} ms");
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
||||
@@ -2902,20 +2902,19 @@ Right-click to remove the key combination, thereby deactivating the shortcut.</v
|
||||
<data name="MouseUtils_GlidingCursor.Description" xml:space="preserve">
|
||||
<value>An accessibility feature that lets you control the mouse with a single button using guided horizontal and vertical lines</value>
|
||||
</data>
|
||||
<data name="MouseUtils_GlidingCursor_InitialSpeed.Header" xml:space="preserve">
|
||||
<data name="MouseUtils_GlidingCursor_InitialSpeed.Header" xml:space="preserve">
|
||||
<value>Initial line speed</value>
|
||||
</data>
|
||||
<data name="MouseUtils_GlidingCursor_InitialSpeed.Description" xml:space="preserve">
|
||||
<data name="MouseUtils_GlidingCursor_InitialSpeed.Description" xml:space="preserve">
|
||||
<value>Speed of the horizontal or vertical line when it begins moving</value>
|
||||
</data>
|
||||
<data name="MouseUtils_GlidingCursor_DelaySpeed.Header" xml:space="preserve">
|
||||
<data name="MouseUtils_GlidingCursor_DelaySpeed.Header" xml:space="preserve">
|
||||
<value>Reduced line speed</value>
|
||||
</data>
|
||||
<data name="MouseUtils_GlidingCursor_DelaySpeed.Description" xml:space="preserve">
|
||||
<data name="MouseUtils_GlidingCursor_DelaySpeed.Description" xml:space="preserve">
|
||||
<value>Speed after slowing down the line with a second shortcut press</value>
|
||||
</data>
|
||||
|
||||
<data name="FancyZones_Radio_Custom_Colors.Content" xml:space="preserve">
|
||||
<data name="FancyZones_Radio_Custom_Colors.Content" xml:space="preserve">
|
||||
<value>Custom colors</value>
|
||||
</data>
|
||||
<data name="FancyZones_Radio_Default_Theme.Content" xml:space="preserve">
|
||||
@@ -5272,7 +5271,7 @@ To record a specific window, enter the hotkey with the Alt key in the opposite m
|
||||
<value>All shortcuts function correctly</value>
|
||||
</data>
|
||||
<data name="ResolveConflicts_Button.Content" xml:space="preserve">
|
||||
<value>Resolve conflicts</value>
|
||||
<value>Resolve conflicts</value>
|
||||
</data>
|
||||
<data name="ShortcutConflictControl_Title.Text" xml:space="preserve">
|
||||
<value>Shortcut conflicts</value>
|
||||
@@ -5293,4 +5292,8 @@ To record a specific window, enter the hotkey with the Alt key in the opposite m
|
||||
<data name="Hosts_NoLeadingSpaces.Description" xml:space="preserve">
|
||||
<value>Do not prepend spaces to active lines when saving the hosts file</value>
|
||||
</data>
|
||||
<data name="Search_ResultsFor" xml:space="preserve">
|
||||
<value>Results for</value>
|
||||
<comment>Prefix for search string. E.g. "Results for 'shortcut'"</comment>
|
||||
</data>
|
||||
</root>
|
||||
@@ -76,6 +76,9 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
// set the callback functions value to handle outgoing IPC message.
|
||||
SendConfigMSG = ipcMSGCallBackFunc;
|
||||
|
||||
// Update the GlobalHotkeyConflictManager with current settings
|
||||
GlobalHotkeyConflictManager.Instance?.UpdateGeneralSettings(generalSettingsConfig);
|
||||
|
||||
foreach (ModuleType moduleType in Enum.GetValues<ModuleType>())
|
||||
{
|
||||
AddDashboardListItem(moduleType);
|
||||
@@ -126,6 +129,9 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
NewPlusViewModel.CopyTemplateExamples(settings.Properties.TemplateLocation.Value);
|
||||
}
|
||||
|
||||
// Update the GlobalHotkeyConflictManager with updated settings
|
||||
GlobalHotkeyConflictManager.Instance?.UpdateGeneralSettings(generalSettingsConfig);
|
||||
|
||||
// Request updated conflicts after module state change
|
||||
RequestConflictData();
|
||||
}
|
||||
@@ -138,6 +144,9 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
|
||||
OnPropertyChanged(nameof(ShortcutModules));
|
||||
|
||||
// Update the GlobalHotkeyConflictManager with updated settings
|
||||
GlobalHotkeyConflictManager.Instance?.UpdateGeneralSettings(generalSettingsConfig);
|
||||
|
||||
// Request updated conflicts after module state change
|
||||
RequestConflictData();
|
||||
}
|
||||
|
||||
@@ -126,14 +126,21 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
{
|
||||
var assembly = Assembly.GetExecutingAssembly();
|
||||
var assemblyName = new AssemblyName(assembly.FullName ?? throw new InvalidOperationException());
|
||||
|
||||
// Build the fully-qualified manifest resource name. Historically, subtle casing differences
|
||||
// (e.g. folder names or the assembly name) caused exact (case-sensitive) lookup failures on
|
||||
// some developer machines when the embedded resource's actual name differed only by case.
|
||||
// Manifest resource name comparison here does not need to be case-sensitive, so we resolve
|
||||
// the actual name using an OrdinalIgnoreCase match, then use the real casing for the stream.
|
||||
var resourceName = $"Microsoft.{assemblyName.Name}.{filename.Replace("/", ".")}";
|
||||
var resourceNames = assembly.GetManifestResourceNames();
|
||||
if (!resourceNames.Contains(resourceName))
|
||||
var actualResourceName = resourceNames.FirstOrDefault(n => string.Equals(n, resourceName, StringComparison.OrdinalIgnoreCase));
|
||||
if (actualResourceName is null)
|
||||
{
|
||||
throw new InvalidOperationException($"Embedded resource '{resourceName}' does not exist.");
|
||||
throw new InvalidOperationException($"Embedded resource '{resourceName}' (case-insensitive) does not exist.");
|
||||
}
|
||||
|
||||
var stream = assembly.GetManifestResourceStream(resourceName)
|
||||
var stream = assembly.GetManifestResourceStream(actualResourceName)
|
||||
?? throw new InvalidOperationException();
|
||||
var image = (Bitmap)Image.FromStream(stream);
|
||||
return image;
|
||||
|
||||
@@ -51,4 +51,5 @@ std::vector<std::wstring> processes =
|
||||
L"PowerToys.WorkspacesWindowArranger.exe",
|
||||
L"PowerToys.WorkspacesEditor.exe",
|
||||
L"PowerToys.ZoomIt.exe",
|
||||
L"Microsoft.CmdPal.UI.exe",
|
||||
};
|
||||
|
||||
21
tools/build/BUILD-GUIDELINES.md
Normal file
21
tools/build/BUILD-GUIDELINES.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# Build scripts - quick guideline
|
||||
|
||||
As the result of our recent changes, use the following guidance when working in the PowerToys repo:
|
||||
|
||||
1. Use `build-essentials.ps1` before any development in general
|
||||
- Purpose: restore NuGet packages for the full solution and build a small set of essential native projects (runner, settings). This is a fast way to ensure native artifacts required for local development are available.
|
||||
|
||||
2. Use `build.ps1` from any folder
|
||||
- Purpose: lightweight local builder. It auto-discovers the target platform (x64/arm64/x86) and builds projects it finds in the current directory.
|
||||
- Notes: you can pass additional MSBuild arguments (e.g. `./tools/build/build.ps1 '/p:CIBuild=true'`) — the script will forward them to MSBuild.
|
||||
- Use `-RestoreOnly` to only restore packages for local projects.
|
||||
|
||||
3. Use `build-installer.ps1` to create a local installer (use with caution)
|
||||
- Purpose: runs the full pipeline that restores, builds the full solution, signs packages, and builds the installer (MSI/bootstrapper).
|
||||
- Caution: this script performs cleaning (git clean) and installer packaging steps that may remove untracked files under `installer/`.
|
||||
|
||||
Additional notes
|
||||
- Shared helpers live in `build-common.ps1` and are used by the other scripts (`RunMSBuild`, `RestoreThenBuild`, `BuildProjectsInDirectory`, platform auto-detection).
|
||||
- If you want a different default platform selection, set the `-Platform` parameter explicitly when invoking the scripts.
|
||||
|
||||
If you want, I can add this guidance to the repository README instead or add a short one-liner comment to the top of `build-common.ps1` so tools can discover it automatically.
|
||||
166
tools/build/build-common.ps1
Normal file
166
tools/build/build-common.ps1
Normal file
@@ -0,0 +1,166 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Shared build helper functions for PowerToys build scripts.
|
||||
|
||||
.DESCRIPTION
|
||||
This file provides reusable helper functions used by the build scripts:
|
||||
- Get-BuildPaths: returns ScriptDir, OriginalCwd, RepoRoot (repo root detection)
|
||||
- RunMSBuild: wrapper around msbuild.exe (accepts optional Platform/Configuration)
|
||||
- RestoreThenBuild: performs restore and optionally builds the solution/project
|
||||
- BuildProjectsInDirectory: discovers and builds local .sln/.csproj/.vcxproj files
|
||||
|
||||
USAGE
|
||||
Dot-source this file from a script to load helpers:
|
||||
. "$PSScriptRoot\build-common.ps1"
|
||||
|
||||
.NOTES
|
||||
Do not execute this file directly; dot-source it from `build.ps1` or `build-installer.ps1` so helpers are available in your script scope.
|
||||
#>
|
||||
|
||||
function RunMSBuild {
|
||||
param (
|
||||
[string]$Solution,
|
||||
[string]$ExtraArgs,
|
||||
[string]$Platform,
|
||||
[string]$Configuration
|
||||
)
|
||||
|
||||
# Prefer the solution's folder for logs; fall back to current directory
|
||||
$logRoot = Split-Path -Path $Solution
|
||||
if (-not $logRoot) { $logRoot = '.' }
|
||||
|
||||
$cfg = $null
|
||||
if ($Configuration) { $cfg = $Configuration.ToLower() } else { $cfg = 'unknown' }
|
||||
$plat = $null
|
||||
if ($Platform) { $plat = $Platform.ToLower() } else { $plat = 'unknown' }
|
||||
|
||||
$allLog = Join-Path $logRoot ("build.{0}.{1}.all.log" -f $cfg, $plat)
|
||||
$warningLog = Join-Path $logRoot ("build.{0}.{1}.warnings.log" -f $cfg, $plat)
|
||||
$errorsLog = Join-Path $logRoot ("build.{0}.{1}.errors.log" -f $cfg, $plat)
|
||||
$binLog = Join-Path $logRoot ("build.{0}.{1}.trace.binlog" -f $cfg, $plat)
|
||||
|
||||
$base = @(
|
||||
$Solution
|
||||
"/p:Platform=$Platform"
|
||||
"/p:Configuration=$Configuration"
|
||||
"/verbosity:normal"
|
||||
'/clp:Summary;PerformanceSummary;ErrorsOnly;WarningsOnly'
|
||||
"/fileLoggerParameters:LogFile=$allLog;Verbosity=detailed"
|
||||
"/fileLoggerParameters1:LogFile=$warningLog;WarningsOnly"
|
||||
"/fileLoggerParameters2:LogFile=$errorsLog;ErrorsOnly"
|
||||
"/bl:$binLog"
|
||||
'/nologo'
|
||||
)
|
||||
|
||||
$cmd = $base + ($ExtraArgs -split ' ')
|
||||
Write-Host (("[MSBUILD] {0}" -f ($cmd -join ' ')))
|
||||
|
||||
Push-Location $script:RepoRoot
|
||||
try {
|
||||
& msbuild.exe @cmd
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Error (("Build failed: {0} {1}" -f $Solution, $ExtraArgs))
|
||||
exit $LASTEXITCODE
|
||||
}
|
||||
} finally {
|
||||
Pop-Location
|
||||
}
|
||||
}
|
||||
|
||||
function RestoreThenBuild {
|
||||
param (
|
||||
[string]$Solution,
|
||||
[string]$ExtraArgs,
|
||||
[string]$Platform,
|
||||
[string]$Configuration,
|
||||
[bool]$RestoreOnly=$false
|
||||
)
|
||||
|
||||
$restoreArgs = '/t:restore /p:RestorePackagesConfig=true'
|
||||
if ($ExtraArgs) { $restoreArgs = "$restoreArgs $ExtraArgs" }
|
||||
RunMSBuild $Solution $restoreArgs $Platform $Configuration
|
||||
|
||||
if (-not $RestoreOnly) {
|
||||
$buildArgs = '/m'
|
||||
if ($ExtraArgs) { $buildArgs = "$buildArgs $ExtraArgs" }
|
||||
RunMSBuild $Solution $buildArgs $Platform $Configuration
|
||||
}
|
||||
}
|
||||
|
||||
function BuildProjectsInDirectory {
|
||||
param(
|
||||
[string]$DirectoryPath,
|
||||
[string]$ExtraArgs,
|
||||
[string]$Platform,
|
||||
[string]$Configuration,
|
||||
[switch]$RestoreOnly
|
||||
)
|
||||
|
||||
if (-not (Test-Path $DirectoryPath)) {
|
||||
return $false
|
||||
}
|
||||
|
||||
$files = @()
|
||||
try {
|
||||
$files = Get-ChildItem -Path (Join-Path $DirectoryPath '*') -Include *.sln,*.csproj,*.vcxproj -File -ErrorAction SilentlyContinue
|
||||
} catch {
|
||||
$files = @()
|
||||
}
|
||||
|
||||
if (-not $files -or $files.Count -eq 0) {
|
||||
return $false
|
||||
}
|
||||
|
||||
$names = ($files | ForEach-Object { $_.Name }) -join ', '
|
||||
Write-Host ("[LOCAL BUILD] Found {0} project(s) in {1}: {2}" -f $files.Count, $DirectoryPath, $names)
|
||||
|
||||
$preferredOrder = @('.sln', '.csproj', '.vcxproj')
|
||||
$files = $files | Sort-Object @{Expression = { [array]::IndexOf($preferredOrder, $_.Extension.ToLower()) }}
|
||||
|
||||
foreach ($f in $files) {
|
||||
Write-Host ("[LOCAL BUILD] Building {0}" -f $f.FullName)
|
||||
if ($f.Extension -eq '.sln') {
|
||||
RestoreThenBuild $f.FullName $ExtraArgs $Platform $Configuration $RestoreOnly
|
||||
} else {
|
||||
$buildArgs = '/m'
|
||||
if ($ExtraArgs) { $buildArgs = "$buildArgs $ExtraArgs" }
|
||||
RunMSBuild $f.FullName $buildArgs $Platform $Configuration
|
||||
}
|
||||
}
|
||||
|
||||
return $true
|
||||
}
|
||||
|
||||
function Get-DefaultPlatform {
|
||||
<#
|
||||
Returns a default target platform string based on the host machine (x64, arm64, x86).
|
||||
#>
|
||||
try {
|
||||
$envArch = $env:PROCESSOR_ARCHITECTURE
|
||||
if ($envArch) { $envArch = $envArch.ToLower() }
|
||||
if ($envArch -eq 'amd64' -or $envArch -eq 'x86_64') { return 'x64' }
|
||||
if ($envArch -match 'arm64') { return 'arm64' }
|
||||
if ($envArch -eq 'x86') { return 'x86' }
|
||||
|
||||
if ($env:PROCESSOR_ARCHITEW6432) {
|
||||
$envArch2 = $env:PROCESSOR_ARCHITEW6432.ToLower()
|
||||
if ($envArch2 -eq 'amd64') { return 'x64' }
|
||||
if ($envArch2 -match 'arm64') { return 'arm64' }
|
||||
}
|
||||
|
||||
try {
|
||||
$osArch = [System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture
|
||||
switch ($osArch.ToString().ToLower()) {
|
||||
'x64' { return 'x64' }
|
||||
'arm64' { return 'arm64' }
|
||||
'x86' { return 'x86' }
|
||||
}
|
||||
} catch {
|
||||
# ignore - RuntimeInformation may not be available
|
||||
}
|
||||
} catch {
|
||||
# ignore any errors and fall back
|
||||
}
|
||||
|
||||
return 'x64'
|
||||
}
|
||||
5
tools/build/build-essentials.cmd
Normal file
5
tools/build/build-essentials.cmd
Normal file
@@ -0,0 +1,5 @@
|
||||
@echo off
|
||||
REM Wrapper to run build-essentials.ps1 from cmd.exe
|
||||
set SCRIPT_DIR=%~dp0
|
||||
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%SCRIPT_DIR%build-essentials.ps1" %*
|
||||
exit /b %ERRORLEVEL%
|
||||
@@ -1,16 +1,71 @@
|
||||
cd $PSScriptRoot
|
||||
cd ..\..
|
||||
$cwd = Get-Location
|
||||
$SolutionDir = $cwd,"" -join "\"
|
||||
cd $SolutionDir
|
||||
$BuildArgs = "/p:Configuration=Release /p:Platform=x64 /p:BuildProjectReferences=false /p:SolutionDir=$SolutionDir"
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Build essential native PowerToys projects (runner and settings), restoring NuGet packages first.
|
||||
|
||||
$ProjectsToBuild =
|
||||
".\src\runner\runner.vcxproj",
|
||||
".\src\modules\shortcut_guide\shortcut_guide.vcxproj",
|
||||
".\src\modules\fancyzones\lib\FancyZonesLib.vcxproj",
|
||||
".\src\modules\fancyzones\dll\FancyZonesModule.vcxproj"
|
||||
.DESCRIPTION
|
||||
Lightweight script to build a small set of essential C++ projects used by PowerToys' runner and native modules. This script first restores NuGet packages for the full solution (`PowerToys.sln`) and then builds the runner and settings projects. Intended for fast local builds during development.
|
||||
|
||||
$ProjectsToBuild | % {
|
||||
Invoke-Expression "msbuild $_ $BuildArgs"
|
||||
.PARAMETER Platform
|
||||
Target platform for the build (for example: 'x64', 'arm64'). If omitted the script will attempt to auto-detect the host platform.
|
||||
|
||||
.PARAMETER Configuration
|
||||
Build configuration (for example: 'Debug' or 'Release'). Default is 'Debug'.
|
||||
|
||||
.EXAMPLE
|
||||
.\tools\build\build-essentials.ps1
|
||||
Restores packages for the solution and builds the default set of native projects using the auto-detected platform and Debug configuration.
|
||||
|
||||
.EXAMPLE
|
||||
.\tools\build\build-essentials.ps1 -Platform arm64 -Configuration Release
|
||||
Restores packages and builds the essentials in Release mode for ARM64, even if your machine is running on x64.
|
||||
|
||||
.NOTES
|
||||
- This script dot-sources `build-common.ps1` and uses the shared helper `RunMSBuild`.
|
||||
- It will call `RestoreThenBuild 'PowerToys.sln'` before building the essential projects to ensure NuGet packages are restored.
|
||||
- The script attempts to locate the repository root automatically and can be run from any folder inside the repo.
|
||||
#>
|
||||
|
||||
param (
|
||||
[string]$Platform = '',
|
||||
[string]$Configuration = 'Debug'
|
||||
)
|
||||
|
||||
# Find repository root starting from the script location
|
||||
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||
$repoRoot = $ScriptDir
|
||||
while ($repoRoot -and -not (Test-Path (Join-Path $repoRoot "PowerToys.sln"))) {
|
||||
$parent = Split-Path -Parent $repoRoot
|
||||
if ($parent -eq $repoRoot) {
|
||||
Write-Error "Could not find PowerToys repository root."
|
||||
exit 1
|
||||
}
|
||||
$repoRoot = $parent
|
||||
}
|
||||
|
||||
# Export script-scope variables used by build-common helpers
|
||||
Set-Variable -Name RepoRoot -Value $repoRoot -Scope Script -Force
|
||||
|
||||
# Load shared helpers
|
||||
. "$PSScriptRoot\build-common.ps1"
|
||||
|
||||
# If platform not provided, auto-detect from host
|
||||
if (-not $Platform -or $Platform -eq '') {
|
||||
try {
|
||||
$Platform = Get-DefaultPlatform
|
||||
Write-Host ("[AUTO-PLATFORM] Detected platform: {0}" -f $Platform)
|
||||
} catch {
|
||||
Write-Warning "Failed to auto-detect platform; defaulting to 'x64'"
|
||||
$Platform = 'x64'
|
||||
}
|
||||
}
|
||||
|
||||
# Ensure solution packages are restored
|
||||
RestoreThenBuild 'PowerToys.sln' '' $Platform $Configuration $true
|
||||
|
||||
# Build both runner and settings
|
||||
$ProjectsToBuild = @(".\src\runner\runner.vcxproj", ".\src\settings-ui\Settings.UI\PowerToys.Settings.csproj")
|
||||
$ExtraArgs = "/p:SolutionDir=$repoRoot\"
|
||||
foreach ($proj in $ProjectsToBuild) {
|
||||
Write-Host ("[BUILD-ESSENTIALS] Building {0}" -f $proj)
|
||||
RunMSBuild $proj $ExtraArgs $Platform $Configuration
|
||||
}
|
||||
@@ -52,12 +52,26 @@ Runs the pipeline for x64 Release with 'vnext' suffix.
|
||||
#>
|
||||
|
||||
param (
|
||||
[string]$Platform = 'x64',
|
||||
[string]$Platform = '',
|
||||
[string]$Configuration = 'Release',
|
||||
[string]$PerUser = 'true',
|
||||
[string]$InstallerSuffix = 'wix5'
|
||||
)
|
||||
|
||||
# Ensure helpers are available
|
||||
. "$PSScriptRoot\build-common.ps1"
|
||||
|
||||
# Auto-detect platform when not provided
|
||||
if (-not $Platform -or $Platform -eq '') {
|
||||
try {
|
||||
$Platform = Get-DefaultPlatform
|
||||
Write-Host ("[AUTO-PLATFORM] Detected platform: {0}" -f $Platform)
|
||||
} catch {
|
||||
Write-Warning "Failed to auto-detect platform; defaulting to x64"
|
||||
$Platform = 'x64'
|
||||
}
|
||||
}
|
||||
|
||||
# Find the PowerToys repository root automatically
|
||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||
$repoRoot = $scriptDir
|
||||
@@ -80,50 +94,7 @@ if (-not $repoRoot -or -not (Test-Path (Join-Path $repoRoot "PowerToys.sln"))) {
|
||||
}
|
||||
|
||||
Write-Host "PowerToys repository root detected: $repoRoot"
|
||||
|
||||
function RunMSBuild {
|
||||
param (
|
||||
[string]$Solution,
|
||||
[string]$ExtraArgs
|
||||
)
|
||||
|
||||
$base = @(
|
||||
$Solution
|
||||
"/p:Platform=$Platform"
|
||||
"/p:Configuration=$Configuration"
|
||||
"/p:CIBuild=true"
|
||||
'/verbosity:normal'
|
||||
'/clp:Summary;PerformanceSummary;ErrorsOnly;WarningsOnly'
|
||||
'/nologo'
|
||||
)
|
||||
|
||||
$cmd = $base + ($ExtraArgs -split ' ')
|
||||
Write-Host ("[MSBUILD] {0} {1}" -f $Solution, ($cmd -join ' '))
|
||||
|
||||
# Run MSBuild from the repository root directory
|
||||
Push-Location $repoRoot
|
||||
try {
|
||||
& msbuild.exe @cmd
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Error ("Build failed: {0} {1}" -f $Solution, $ExtraArgs)
|
||||
exit $LASTEXITCODE
|
||||
}
|
||||
} finally {
|
||||
Pop-Location
|
||||
}
|
||||
}
|
||||
|
||||
function RestoreThenBuild {
|
||||
param ([string]$Solution)
|
||||
|
||||
# 1) restore
|
||||
RunMSBuild $Solution '/t:restore /p:RestorePackagesConfig=true'
|
||||
# 2) build -------------------------------------------------
|
||||
RunMSBuild $Solution '/m'
|
||||
}
|
||||
|
||||
# WiX v5 projects use WixToolset.Sdk via NuGet/MSBuild; a separate WiX 3 installation is not required here.
|
||||
|
||||
Write-Host ("[PIPELINE] Start | Platform={0} Configuration={1} PerUser={2}" -f $Platform, $Configuration, $PerUser)
|
||||
Write-Host ''
|
||||
|
||||
@@ -134,7 +105,9 @@ if (Test-Path $cmdpalOutputPath) {
|
||||
Remove-Item $cmdpalOutputPath -Recurse -Force -ErrorAction Ignore
|
||||
}
|
||||
|
||||
RestoreThenBuild 'PowerToys.sln'
|
||||
$commonArgs = '/p:CIBuild=true'
|
||||
# No local projects found (or continuing) - build full solution and tools
|
||||
RestoreThenBuild 'PowerToys.sln' $commonArgs $Platform $Configuration
|
||||
|
||||
$msixSearchRoot = Join-Path $repoRoot "$Platform\$Configuration"
|
||||
$msixFiles = Get-ChildItem -Path $msixSearchRoot -Recurse -Filter *.msix |
|
||||
@@ -148,8 +121,8 @@ else {
|
||||
Write-Warning "[SIGN] No .msix files found in $msixSearchRoot"
|
||||
}
|
||||
|
||||
RestoreThenBuild 'tools\BugReportTool\BugReportTool.sln'
|
||||
RestoreThenBuild 'tools\StylesReportTool\StylesReportTool.sln'
|
||||
RestoreThenBuild 'tools\BugReportTool\BugReportTool.sln' $commonArgs $Platform $Configuration
|
||||
RestoreThenBuild 'tools\StylesReportTool\StylesReportTool.sln' $commonArgs $Platform $Configuration
|
||||
|
||||
Write-Host '[CLEAN] installer (keep *.exe)'
|
||||
Push-Location $repoRoot
|
||||
@@ -159,10 +132,10 @@ try {
|
||||
Pop-Location
|
||||
}
|
||||
|
||||
RunMSBuild 'installer\PowerToysSetup.sln' '/t:restore /p:RestorePackagesConfig=true'
|
||||
RunMSBuild 'installer\PowerToysSetup.sln' "$commonArgs /t:restore /p:RestorePackagesConfig=true" $Platform $Configuration
|
||||
|
||||
RunMSBuild 'installer\PowerToysSetup.sln' "/m /t:PowerToysInstallerVNext /p:PerUser=$PerUser /p:InstallerSuffix=$InstallerSuffix"
|
||||
RunMSBuild 'installer\PowerToysSetup.sln' "$commonArgs /m /t:PowerToysInstallerVNext /p:PerUser=$PerUser /p:InstallerSuffix=$InstallerSuffix" $Platform $Configuration
|
||||
|
||||
RunMSBuild 'installer\PowerToysSetup.sln' "/m /t:PowerToysBootstrapperVNext /p:PerUser=$PerUser /p:InstallerSuffix=$InstallerSuffix"
|
||||
RunMSBuild 'installer\PowerToysSetup.sln' "$commonArgs /m /t:PowerToysBootstrapperVNext /p:PerUser=$PerUser /p:InstallerSuffix=$InstallerSuffix" $Platform $Configuration
|
||||
|
||||
Write-Host '[PIPELINE] Completed'
|
||||
|
||||
5
tools/build/build.cmd
Normal file
5
tools/build/build.cmd
Normal file
@@ -0,0 +1,5 @@
|
||||
@echo off
|
||||
REM Wrapper to run the PowerShell build script from cmd.exe
|
||||
set SCRIPT_DIR=%~dp0
|
||||
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%SCRIPT_DIR%build.ps1" %*
|
||||
exit /b %ERRORLEVEL%
|
||||
88
tools/build/build.ps1
Normal file
88
tools/build/build.ps1
Normal file
@@ -0,0 +1,88 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Light-weight wrapper to build local projects (solutions/projects) in the current working directory using helpers in build-common.ps1.
|
||||
|
||||
.DESCRIPTION
|
||||
This script is intended for quick local builds. It dot-sources `build-common.ps1` and calls `BuildProjectsInDirectory` against the current directory. Use `-RestoreOnly` to only restore packages for local projects. If `-Platform` is omitted the script attempts to auto-detect the host platform.
|
||||
|
||||
.PARAMETER Platform
|
||||
Target platform (e.g., 'x64', 'arm64'). If omitted the script will try to detect the host platform automatically.
|
||||
|
||||
.PARAMETER Configuration
|
||||
Build configuration (e.g., 'Debug', 'Release'). Default: 'Debug'.
|
||||
|
||||
.PARAMETER RestoreOnly
|
||||
If specified, only perform package restore for local projects and skip the build steps for a solution file (i.e. .sln).
|
||||
|
||||
.PARAMETER ExtraArgs
|
||||
Any remaining, positional arguments passed to the script are forwarded to MSBuild as additional arguments (e.g., '/p:CIBuild=true').
|
||||
|
||||
.EXAMPLE
|
||||
.\tools\build\build.ps1
|
||||
Builds any .sln/.csproj/.vcxproj in the current working directory (auto-detects Platform).
|
||||
|
||||
.EXAMPLE
|
||||
.\tools\build\build.ps1 -Platform x64 -Configuration Release
|
||||
Builds local projects for x64 Release.
|
||||
|
||||
.EXAMPLE
|
||||
.\tools\build\build.ps1 '/p:CIBuild=true' '/p:SomeOther=Value'
|
||||
Pass additional MSBuild arguments; these are forwarded to the underlying msbuild calls.
|
||||
|
||||
.EXAMPLE
|
||||
.\tools\build\build.ps1 -RestoreOnly '/p:CIBuild=true'
|
||||
Only restores packages for local projects; ExtraArgs still forwarded to msbuild's restore phase.
|
||||
|
||||
.NOTES
|
||||
- This file expects `build-common.ps1` to be located in the same folder and dot-sources it to load helper functions.
|
||||
- ExtraArgs are captured using PowerShell's ValueFromRemainingArguments and joined before being passed to the helpers.
|
||||
#>
|
||||
|
||||
param (
|
||||
[string]$Platform = '',
|
||||
[string]$Configuration = 'Debug',
|
||||
[switch]$RestoreOnly,
|
||||
[Parameter(ValueFromRemainingArguments=$true)]
|
||||
[string[]]$ExtraArgs
|
||||
)
|
||||
|
||||
. "$PSScriptRoot\build-common.ps1"
|
||||
|
||||
# If user passed MSBuild-style args (e.g. './build.ps1 /p:CIBuild=true'),
|
||||
# those will bind to $Platform/$Configuration; detect those and move them to ExtraArgs.
|
||||
$positionalExtra = @()
|
||||
if ($Platform -and $Platform -match '^[\/-]') {
|
||||
$positionalExtra += $Platform
|
||||
$Platform = ''
|
||||
}
|
||||
if ($Configuration -and $Configuration -match '^[\/-]') {
|
||||
$positionalExtra += $Configuration
|
||||
$Configuration = 'Debug'
|
||||
}
|
||||
if ($positionalExtra.Count -gt 0) {
|
||||
if (-not $ExtraArgs) { $ExtraArgs = @() }
|
||||
$ExtraArgs = $positionalExtra + $ExtraArgs
|
||||
}
|
||||
|
||||
# Auto-detect platform when not provided
|
||||
if (-not $Platform -or $Platform -eq '') {
|
||||
try {
|
||||
$Platform = Get-DefaultPlatform
|
||||
Write-Host ("[AUTO-PLATFORM] Detected platform: {0}" -f $Platform)
|
||||
} catch {
|
||||
Write-Warning "Failed to auto-detect platform; defaulting to x64"
|
||||
$Platform = 'x64'
|
||||
}
|
||||
}
|
||||
|
||||
$cwd = (Get-Location).ProviderPath
|
||||
$extraArgsString = $null
|
||||
if ($ExtraArgs -and $ExtraArgs.Count -gt 0) { $extraArgsString = ($ExtraArgs -join ' ') }
|
||||
|
||||
if (BuildProjectsInDirectory -DirectoryPath $cwd -ExtraArgs $extraArgsString -Platform $Platform -Configuration $Configuration -RestoreOnly:$RestoreOnly) {
|
||||
Write-Host "[BUILD] Local projects built; exiting."
|
||||
exit 0
|
||||
} else {
|
||||
Write-Host "[BUILD] No local projects found in $cwd"
|
||||
exit 0
|
||||
}
|
||||
Reference in New Issue
Block a user