Compare commits

..

1 Commits

Author SHA1 Message Date
Leilei Zhang
6a0abecba2 add runner 2025-07-14 11:53:10 +08:00
42 changed files with 159 additions and 661 deletions

View File

@@ -1383,7 +1383,6 @@ RIGHTSCROLLBAR
riid
RKey
RNumber
Rns
rop
ROUNDSMALL
ROWSETEXT

View File

@@ -38,11 +38,6 @@ parameters:
displayName: "Build Using Visual Studio Preview"
default: false
- name: enableAOT
type: boolean
displayName: "Enable AOT (Ahead-of-Time) Compilation for CmdPal"
default: true
name: $(BuildDefinitionName)_$(date:yyMM).$(date:dd)$(rev:rrr)
variables:
@@ -100,7 +95,7 @@ extends:
useManagedIdentity: $(SigningUseManagedIdentity)
clientId: $(SigningOriginalClientId)
# Have msbuild use the release nuget config profile
additionalBuildOptions: /p:RestoreConfigFile="$(Build.SourcesDirectory)\.pipelines\release-nuget.config" /p:EnableCmdPalAOT=${{ parameters.enableAOT }}
additionalBuildOptions: /p:RestoreConfigFile="$(Build.SourcesDirectory)\.pipelines\release-nuget.config"
beforeBuildSteps:
# Sets versions for all PowerToy created DLLs
- pwsh: |-

View File

@@ -19,7 +19,7 @@ Get-ChildItem $targetDir -Recurse -Filter *.deps.json -Exclude *UITest*,MouseJum
# Temporarily exclude All UI-Test, Fuzzer-Test projects because of Appium.WebDriver dependencies
$depsJsonFullFileName = $_.FullName
if ($depsJsonFullFileName -like "*CmdPal*" -or $depsJsonFullFileName -like "*CommandPalette*") {
if ($depsJsonFullFileName -like "*CmdPal*") {
return
}

View File

@@ -21,7 +21,6 @@
<PackageVersion Include="CommunityToolkit.WinUI.Extensions" Version="8.2.250402" />
<PackageVersion Include="CommunityToolkit.WinUI.UI.Controls.DataGrid" Version="7.1.2" />
<PackageVersion Include="CommunityToolkit.WinUI.UI.Controls.Markdown" Version="7.1.2" />
<PackageVersion Include="CommunityToolkit.Labs.WinUI.Controls.MarkdownTextBlock" Version="0.1.250703-build.2173" />
<PackageVersion Include="ControlzEx" Version="6.0.0" />
<PackageVersion Include="HelixToolkit" Version="2.24.0" />
<PackageVersion Include="HelixToolkit.Core.Wpf" Version="2.24.0" />

View File

@@ -1497,7 +1497,6 @@ SOFTWARE.
- Appium.WebDriver 4.4.5
- Azure.AI.OpenAI 1.0.0-beta.17
- CommunityToolkit.Common 8.4.0
- CommunityToolkit.Labs.WinUI.Controls.MarkdownTextBlock 0.1.250703-build.2173
- CommunityToolkit.Mvvm 8.4.0
- CommunityToolkit.WinUI.Animations 8.2.250402
- CommunityToolkit.WinUI.Collections 8.2.250402
@@ -1580,3 +1579,4 @@ SOFTWARE.
- WinUIEx 2.2.0
- WPF-UI 3.0.5
- WyHash 1.0.5

View File

@@ -21,7 +21,7 @@ Welcome to the PowerToys developer documentation. This documentation provides in
- [Coding Guidelines](development/guidelines.md) - Development guidelines and best practices
- [Coding Style](development/style.md) - Code formatting and style conventions
- [UI Testing](UITests.md) - How to write UI tests for PowerToys
- [UI Testing](development/ui-tests.md) - How to write UI tests for PowerToys
- [Debugging](development/debugging.md) - Techniques for debugging PowerToys
## Tools

View File

@@ -2,10 +2,10 @@
<configuration>
<packageSources>
<clear />
<add key="PowerToysPublicDependencies" value="https://pkgs.dev.azure.com/shine-oss/PowerToys/_packaging/PowerToysPublicDependencies/nuget/v3/index.json" />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
</packageSources>
<packageSourceMapping>
<packageSource key="PowerToysPublicDependencies">
<packageSource key="nuget.org">
<package pattern="*" />
</packageSource>
</packageSourceMapping>

View File

@@ -7,7 +7,6 @@
<CsWinRTAotWarningLevel>2</CsWinRTAotWarningLevel>
<!-- Suppress DynamicallyAccessedMemberTypes.PublicParameterlessConstructor in fallback code path of Windows SDK projection -->
<!-- Suppress CA1416 for Windows-specific APIs that are used in PowerToys which only runs on Windows 10.0.19041.0+ -->
<WarningsNotAsErrors>IL2081;CsWinRT1028;CA1416;$(WarningsNotAsErrors)</WarningsNotAsErrors>
<WarningsNotAsErrors>IL2081;CsWinRT1028;$(WarningsNotAsErrors)</WarningsNotAsErrors>
</PropertyGroup>
</Project>

View File

@@ -122,23 +122,31 @@ namespace Microsoft.PowerToys.UITest
public void StartExe(string appPath, string[]? args = null)
{
var opts = new AppiumOptions();
opts.AddAdditionalCapability("app", appPath);
if (args != null && args.Length > 0)
if (scope == PowerToysModule.PowerToysSettings)
{
// Build command line arguments string
string argsString = string.Join(" ", args.Select(arg =>
TryLaunchPowerToysSettings(opts);
}
else
{
opts.AddAdditionalCapability("app", appPath);
if (args != null && args.Length > 0)
{
// Quote arguments that contain spaces
if (arg.Contains(' '))
// Build command line arguments string
string argsString = string.Join(" ", args.Select(arg =>
{
return $"\"{arg}\"";
}
// Quote arguments that contain spaces
if (arg.Contains(' '))
{
return $"\"{arg}\"";
}
return arg;
}));
return arg;
}));
opts.AddAdditionalCapability("appArguments", argsString);
opts.AddAdditionalCapability("appArguments", argsString);
}
}
this.Driver = NewWindowsDriver(opts);

View File

@@ -249,19 +249,7 @@ public partial class PageViewModel : ExtensionObjectViewModel, IPageContext
public interface IPageContext
{
void ShowException(Exception ex, string? extensionHint = null);
public void ShowException(Exception ex, string? extensionHint = null);
TaskScheduler Scheduler { get; }
}
public interface IPageViewModelFactoryService
{
/// <summary>
/// Creates a new instance of the page view model for the given page type.
/// </summary>
/// <param name="page">The page for which to create the view model.</param>
/// <param name="nested">Indicates whether the page is not the top-level page.</param>
/// <param name="host">The command palette host that will host the page (for status messages)</param>
/// <returns>A new instance of the page view model.</returns>
PageViewModel? TryCreatePageViewModel(IPage page, bool nested, CommandPaletteHost host);
public TaskScheduler Scheduler { get; }
}

View File

@@ -1,27 +0,0 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.CommandPalette.Extensions;
namespace Microsoft.CmdPal.UI.ViewModels;
public class PageViewModelFactory : IPageViewModelFactoryService
{
private readonly TaskScheduler _scheduler;
public PageViewModelFactory(TaskScheduler scheduler)
{
_scheduler = scheduler;
}
public PageViewModel? TryCreatePageViewModel(IPage page, bool nested, CommandPaletteHost host)
{
return page switch
{
IListPage listPage => new ListViewModel(listPage, _scheduler, host) { IsNested = nested },
IContentPage contentPage => new ContentPageViewModel(contentPage, _scheduler, host),
_ => null,
};
}
}

View File

@@ -21,7 +21,6 @@ public partial class ShellViewModel : ObservableObject,
{
private readonly IRootPageService _rootPageService;
private readonly TaskScheduler _scheduler;
private readonly IPageViewModelFactoryService _pageViewModelFactory;
private readonly Lock _invokeLock = new();
private Task? _handleInvokeTask;
@@ -66,9 +65,8 @@ public partial class ShellViewModel : ObservableObject,
public bool IsNested { get => _isNested; }
public ShellViewModel(TaskScheduler scheduler, IRootPageService rootPageService, IPageViewModelFactoryService pageViewModelFactory)
public ShellViewModel(TaskScheduler scheduler, IRootPageService rootPageService)
{
_pageViewModelFactory = pageViewModelFactory;
_scheduler = scheduler;
_rootPageService = rootPageService;
_currentPage = new LoadingPageViewModel(null, _scheduler);
@@ -254,7 +252,7 @@ public partial class ShellViewModel : ObservableObject,
_isNested = !isMainPage;
// Construct our ViewModel of the appropriate type and pass it the UI Thread context.
var pageViewModel = _pageViewModelFactory.TryCreatePageViewModel(page, _isNested, host);
var pageViewModel = GetViewModelForPage(page, _isNested, host);
if (pageViewModel == null)
{
Logger.LogError($"Failed to create ViewModel for page {page.GetType().Name}");
@@ -399,6 +397,19 @@ public partial class ShellViewModel : ObservableObject,
}
}
private PageViewModel? GetViewModelForPage(IPage page, bool nested, CommandPaletteHost host)
{
return page switch
{
IListPage listPage => new ListViewModel(listPage, _scheduler, host)
{
IsNested = nested,
},
IContentPage contentPage => new ContentPageViewModel(contentPage, _scheduler, host),
_ => null,
};
}
public void SetActiveExtension(IExtensionWrapper? extension)
{
if (extension != _activeExtension)

View File

@@ -104,14 +104,14 @@ public partial class TopLevelCommandManager : ObservableObject,
List<TopLevelViewModel> commands = [];
foreach (var item in commandProvider.TopLevelItems)
{
commands.Add(item);
TopLevelCommands.Add(item);
}
foreach (var item in commandProvider.FallbackItems)
{
if (item.IsEnabled)
{
commands.Add(item);
TopLevelCommands.Add(item);
}
}

View File

@@ -149,7 +149,6 @@ public partial class App : Application
// ViewModels
services.AddSingleton<ShellViewModel>();
services.AddSingleton<IPageViewModelFactoryService, PageViewModelFactory>();
return services.BuildServiceProvider();
}

View File

@@ -4,8 +4,8 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:Microsoft.CmdPal.UI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:labToolkit="using:CommunityToolkit.Labs.WinUI.MarkdownTextBlock"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:tk7controls="using:CommunityToolkit.WinUI.UI.Controls"
x:Name="ShortcutContentControl"
mc:Ignorable="d">
<Grid MinWidth="498" MinHeight="220">
@@ -66,7 +66,7 @@
IsTabStop="{Binding ElementName=ShortcutContentControl, Path=IsWarningAltGr, Mode=OneWay}"
Severity="Warning" />
</Grid>
<labToolkit:MarkdownTextBlock
<tk7controls:MarkdownTextBlock
x:Uid="InvalidShortcutWarningLabel"
Background="Transparent"
FontSize="12"

View File

@@ -4,8 +4,8 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:Microsoft.CmdPal.UI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:labToolkit="using:CommunityToolkit.Labs.WinUI.MarkdownTextBlock"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:tk7controls="using:CommunityToolkit.WinUI.UI.Controls"
d:DesignHeight="300"
d:DesignWidth="400"
mc:Ignorable="d">
@@ -36,7 +36,7 @@
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<labToolkit:MarkdownTextBlock
<tk7controls:MarkdownTextBlock
Grid.Column="1"
VerticalAlignment="Center"
Background="Transparent"

View File

@@ -10,9 +10,7 @@
xmlns:controls="using:CommunityToolkit.WinUI.Controls"
xmlns:converters="using:CommunityToolkit.WinUI.Converters"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:labToolkit="using:CommunityToolkit.Labs.WinUI.MarkdownTextBlock"
xmlns:local="using:Microsoft.CmdPal.UI"
xmlns:markdownTextBlockRns="using:CommunityToolkit.WinUI.Controls.MarkdownTextBlockRns"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:toolkit="using:CommunityToolkit.WinUI.UI.Controls"
xmlns:viewModels="using:Microsoft.CmdPal.UI.ViewModels"
@@ -21,12 +19,6 @@
<Page.Resources>
<ResourceDictionary>
<markdownTextBlockRns:MarkdownThemes
x:Key="DefaultMarkdownThemeConfig"
H3FontSize="12"
H3FontWeight="Normal" />
<labToolkit:MarkdownConfig x:Key="DefaultMarkdownConfig" Themes="{StaticResource DefaultMarkdownThemeConfig}" />
<StackLayout
x:Name="VerticalStackLayout"
Orientation="Vertical"
@@ -51,9 +43,12 @@
<DataTemplate x:Key="MarkdownContentTemplate" x:DataType="viewModels:ContentMarkdownViewModel">
<Grid Margin="0,4,4,4" Padding="12,8,8,8">
<labToolkit:MarkdownTextBlock
<toolkit:MarkdownTextBlock
Background="Transparent"
Config="{StaticResource DefaultMarkdownConfig}"
Header3FontSize="12"
Header3FontWeight="Normal"
Header3Foreground="{ThemeResource TextFillColorSecondaryBrush}"
IsTextSelectionEnabled="True"
Text="{x:Bind Body, Mode=OneWay}" />
</Grid>
</DataTemplate>
@@ -66,9 +61,12 @@
<DataTemplate x:Key="NestedMarkdownContentTemplate" x:DataType="viewModels:ContentMarkdownViewModel">
<Grid>
<labToolkit:MarkdownTextBlock
<toolkit:MarkdownTextBlock
Background="Transparent"
Config="{StaticResource DefaultMarkdownConfig}"
Header3FontSize="12"
Header3FontWeight="Normal"
Header3Foreground="{ThemeResource TextFillColorSecondaryBrush}"
IsTextSelectionEnabled="True"
Text="{x:Bind Body, Mode=OneWay}" />
</Grid>
</DataTemplate>

View File

@@ -1,6 +1,5 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\Common.Dotnet.CsWinRT.props" />
<Import Project="..\..\..\Common.Dotnet.AotCompatibility.props" />
<Import Project="..\..\..\CmdPalVersion.props" />
<Import Project="CmdPal.pre.props" />
<Import Project="CmdPal.Branding.props" />
@@ -24,13 +23,6 @@
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
</PropertyGroup>
<PropertyGroup Condition="'$(EnableCmdPalAOT)' == 'true'">
<SelfContained>true</SelfContained>
<PublishSingleFile>false</PublishSingleFile>
<DisableRuntimeMarshalling>false</DisableRuntimeMarshalling>
<PublishAot>true</PublishAot>
</PropertyGroup>
<PropertyGroup Condition="'$(CIBuild)'=='true'">
<GenerateAppxPackageOnBuild>true</GenerateAppxPackageOnBuild>
</PropertyGroup>
@@ -79,7 +71,7 @@
<PackageReference Include="CommunityToolkit.WinUI.Converters" />
<PackageReference Include="CommunityToolkit.WinUI.Animations" />
<PackageReference Include="CommunityToolkit.WinUI.Extensions" />
<PackageReference Include="CommunityToolkit.Labs.WinUI.Controls.MarkdownTextBlock" />
<PackageReference Include="CommunityToolkit.WinUI.UI.Controls.Markdown" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" />
<PackageReference Include="Microsoft.WindowsAppSDK" />

View File

@@ -22,6 +22,7 @@ SetActiveWindow
MonitorFromWindow
GetMonitorInfo
GetDpiForMonitor
CoAllowSetForegroundWindow
WM_HOTKEY
WM_NCLBUTTONDBLCLK

View File

@@ -9,10 +9,8 @@
xmlns:cpcontrols="using:Microsoft.CmdPal.UI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:help="using:Microsoft.CmdPal.UI.Helpers"
xmlns:labToolkit="using:CommunityToolkit.Labs.WinUI.MarkdownTextBlock"
xmlns:markdownTextBlockRns="using:CommunityToolkit.WinUI.Controls.MarkdownTextBlockRns"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:toolkit="using:CommunityToolkit.WinUI.Controls"
xmlns:toolkit="using:CommunityToolkit.WinUI.UI.Controls"
xmlns:ui="using:CommunityToolkit.WinUI"
xmlns:viewModels="using:Microsoft.CmdPal.UI.ViewModels"
Background="Transparent"
@@ -146,11 +144,6 @@
</ItemsControl>
</StackPanel>
</DataTemplate>
<markdownTextBlockRns:MarkdownThemes
x:Key="DefaultMarkdownThemeConfig"
H3FontSize="12"
H3FontWeight="Normal" />
<labToolkit:MarkdownConfig x:Key="DefaultMarkdownConfig" Themes="{StaticResource DefaultMarkdownThemeConfig}" />
</ResourceDictionary>
</Page.Resources>
@@ -413,11 +406,14 @@
TextWrapping="WrapWholeWords"
Visibility="{x:Bind ViewModel.Details.Title, Converter={StaticResource StringNotEmptyToVisibilityConverter}, Mode=OneWay}" />
<labToolkit:MarkdownTextBlock
<toolkit:MarkdownTextBlock
Grid.Row="2"
Margin="0,4,0,24"
Background="Transparent"
Config="{StaticResource DefaultMarkdownConfig}"
Header3FontSize="12"
Header3FontWeight="Normal"
Header3Foreground="{ThemeResource TextFillColorSecondaryBrush}"
IsTextSelectionEnabled="True"
Text="{x:Bind ViewModel.Details.Body, Mode=OneWay}" />
<ItemsRepeater

View File

@@ -13,7 +13,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
<PublishSingleFile>False</PublishSingleFile>
<PublishReadyToRun Condition="'$(Configuration)' == 'Debug'">False</PublishReadyToRun>
<PublishReadyToRun Condition="'$(Configuration)' != 'Debug'">True</PublishReadyToRun>
<PublishTrimmed Condition="'$(EnableCmdPalAOT)' == 'true'">True</PublishTrimmed>
<PublishTrimmed Condition="'$(EnableCmdPalAOT)' != 'true'">False</PublishTrimmed>
<PublishTrimmed Condition="'$(Configuration)' == 'Debug'">False</PublishTrimmed>
<PublishTrimmed Condition="'$(Configuration)' != 'Debug'">False</PublishTrimmed>
</PropertyGroup>
</Project>

View File

@@ -13,7 +13,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
<PublishSingleFile>False</PublishSingleFile>
<PublishReadyToRun Condition="'$(Configuration)' == 'Debug'">False</PublishReadyToRun>
<PublishReadyToRun Condition="'$(Configuration)' != 'Debug'">True</PublishReadyToRun>
<PublishTrimmed Condition="'$(EnableCmdPalAOT)' == 'true'">True</PublishTrimmed>
<PublishTrimmed Condition="'$(EnableCmdPalAOT)' != 'true'">False</PublishTrimmed>
<PublishTrimmed Condition="'$(Configuration)' == 'Debug'">False</PublishTrimmed>
<PublishTrimmed Condition="'$(Configuration)' != 'Debug'">False</PublishTrimmed>
</PropertyGroup>
</Project>

View File

@@ -2,12 +2,8 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
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;
using Microsoft.CommandPalette.Extensions.Toolkit;
@@ -33,12 +29,9 @@ public partial class AllAppsCommandProvider : CommandProvider
Subtitle = Resources.search_installed_apps,
MoreCommands = [new CommandContextItem(AllAppsSettings.Instance.Settings.SettingsPage)],
};
// Subscribe to pin state changes to refresh the command provider
PinnedAppsManager.Instance.PinStateChanged += OnPinStateChanged;
}
public override ICommandItem[] TopLevelCommands() => [_listItem, ..Page.GetPinnedApps()];
public override ICommandItem[] TopLevelCommands() => [_listItem];
public ICommandItem? LookupApp(string displayName)
{
@@ -69,9 +62,4 @@ public partial class AllAppsCommandProvider : CommandProvider
return null;
}
private void OnPinStateChanged(object? sender, System.EventArgs e)
{
RaiseItemsChanged(0);
}
}

View File

@@ -2,20 +2,14 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using ManagedCommon;
using Microsoft.CmdPal.Ext.Apps.Commands;
using Microsoft.CmdPal.Ext.Apps.Helpers;
using Microsoft.CmdPal.Ext.Apps.Programs;
using Microsoft.CmdPal.Ext.Apps.Properties;
using Microsoft.CmdPal.Ext.Apps.State;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
@@ -24,22 +18,16 @@ namespace Microsoft.CmdPal.Ext.Apps;
public sealed partial class AllAppsPage : ListPage
{
private readonly Lock _listLock = new();
private AppItem[] allApps = [];
private AppListItem[] unpinnedApps = [];
private AppListItem[] pinnedApps = [];
private AppListItem[] allAppsSection = [];
public AllAppsPage()
{
this.Name = Resources.all_apps;
this.Icon = Icons.AllAppsIcon;
this.Icon = IconHelpers.FromRelativePath("Assets\\AllApps.svg");
this.ShowDetails = true;
this.IsLoading = true;
this.PlaceholderText = Resources.search_installed_apps_placeholder;
// Subscribe to pin state changes to refresh the command provider
PinnedAppsManager.Instance.PinStateChanged += OnPinStateChanged;
Task.Run(() =>
{
lock (_listLock)
@@ -49,139 +37,89 @@ public sealed partial class AllAppsPage : ListPage
});
}
internal AppListItem[] GetPinnedApps()
{
BuildListItems();
return pinnedApps;
}
public override IListItem[] GetItems()
{
// Build or update the list if needed
BuildListItems();
return pinnedApps.Concat(unpinnedApps).ToArray();
if (allAppsSection.Length == 0 || AppCache.Instance.Value.ShouldReload())
{
lock (_listLock)
{
BuildListItems();
}
}
return allAppsSection;
}
private void BuildListItems()
{
if (allApps.Length == 0 || AppCache.Instance.Value.ShouldReload())
{
lock (_listLock)
{
this.IsLoading = true;
this.IsLoading = true;
Stopwatch stopwatch = new();
stopwatch.Start();
Stopwatch stopwatch = new();
stopwatch.Start();
var apps = GetPrograms();
this.allApps = apps.AllApps;
this.pinnedApps = apps.PinnedItems;
this.unpinnedApps = apps.UnpinnedItems;
var apps = GetPrograms();
this.IsLoading = false;
this.allAppsSection = apps
.Select((app) => new AppListItem(app, true))
.ToArray();
AppCache.Instance.Value.ResetReloadFlag();
this.IsLoading = false;
stopwatch.Stop();
Logger.LogTrace($"{nameof(AllAppsPage)}.{nameof(BuildListItems)} took: {stopwatch.ElapsedMilliseconds} ms");
}
}
AppCache.Instance.Value.ResetReloadFlag();
stopwatch.Stop();
Logger.LogTrace($"{nameof(AllAppsPage)}.{nameof(BuildListItems)} took: {stopwatch.ElapsedMilliseconds} ms");
}
private AppItem[] GetAllApps()
internal List<AppItem> GetPrograms()
{
var uwpResults = AppCache.Instance.Value.UWPs
.Where((application) => application.Enabled)
.Select(app => app.ToAppItem());
.Where((application) => application.Enabled)
.Select(UwpToAppItem);
var win32Results = AppCache.Instance.Value.Win32s
.Where((application) => application.Enabled && application.Valid)
.Select(app => app.ToAppItem());
.Select(app =>
{
var icoPath = string.IsNullOrEmpty(app.IcoPath) ?
(app.AppType == Win32Program.ApplicationType.InternetShortcutApplication ?
app.IcoPath :
app.FullPath) :
app.IcoPath;
var allApps = uwpResults.Concat(win32Results).ToArray();
return allApps;
// icoPath = icoPath.EndsWith(".lnk", System.StringComparison.InvariantCultureIgnoreCase) ? (icoPath + ",0") : icoPath;
icoPath = icoPath.EndsWith(".lnk", System.StringComparison.InvariantCultureIgnoreCase) ?
app.FullPath :
icoPath;
return new AppItem()
{
Name = app.Name,
Subtitle = app.Description,
Type = app.Type(),
IcoPath = icoPath,
ExePath = !string.IsNullOrEmpty(app.LnkFilePath) ? app.LnkFilePath : app.FullPath,
DirPath = app.Location,
Commands = app.GetCommands(),
};
});
return uwpResults.Concat(win32Results).OrderBy(app => app.Name).ToList();
}
internal (AppItem[] AllApps, AppListItem[] PinnedItems, AppListItem[] UnpinnedItems) GetPrograms()
private AppItem UwpToAppItem(UWPApplication app)
{
var allApps = GetAllApps();
var pinned = new List<AppListItem>();
var unpinned = new List<AppListItem>();
foreach (var app in allApps)
var iconPath = app.LogoType != LogoType.Error ? app.LogoPath : string.Empty;
var item = new AppItem()
{
var isPinned = PinnedAppsManager.Instance.IsAppPinned(app.AppIdentifier);
var appListItem = new AppListItem(app, true, isPinned);
if (isPinned)
{
appListItem.Tags = appListItem.Tags
.Concat([new Tag() { Icon = Icons.PinIcon }])
.ToArray();
pinned.Add(appListItem);
}
else
{
unpinned.Add(appListItem);
}
}
return (
allApps
.ToArray(),
pinned
.OrderBy(app => app.Title)
.ToArray(),
unpinned
.OrderBy(app => app.Title)
.ToArray());
}
private void OnPinStateChanged(object? sender, PinStateChangedEventArgs e)
{
/*
* Rebuilding all the lists is pretty expensive.
* So, instead, we'll just compare pinned items to move existing
* items between the two lists.
*/
var existingAppItem = allApps.FirstOrDefault(f => f.AppIdentifier == e.AppIdentifier);
if (existingAppItem != null)
{
var appListItem = new AppListItem(existingAppItem, true, e.IsPinned);
if (e.IsPinned)
{
// Remove it from the unpinned apps array
this.unpinnedApps = this.unpinnedApps
.Where(app => app.AppIdentifier != existingAppItem.AppIdentifier)
.OrderBy(app => app.Title)
.ToArray();
var newPinned = this.pinnedApps.ToList();
newPinned.Add(appListItem);
this.pinnedApps = newPinned
.OrderBy(app => app.Title)
.ToArray();
}
else
{
// Remove it from the pinned apps array
this.pinnedApps = this.pinnedApps
.Where(app => app.AppIdentifier != existingAppItem.AppIdentifier)
.OrderBy(app => app.Title)
.ToArray();
var newUnpinned = this.unpinnedApps.ToList();
newUnpinned.Add(appListItem);
this.unpinnedApps = newUnpinned
.OrderBy(app => app.Title)
.ToArray();
}
RaiseItemsChanged(0);
}
Name = app.Name,
Subtitle = app.Description,
Type = UWPApplication.Type(),
IcoPath = iconPath,
DirPath = app.Location,
UserModelId = app.UserModelId,
IsPackaged = true,
Commands = app.GetCommands(),
};
return item;
}
}

View File

@@ -4,7 +4,6 @@
using System.Collections.Generic;
using Microsoft.CmdPal.Ext.Apps.Programs;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
namespace Microsoft.CmdPal.Ext.Apps;
@@ -27,9 +26,7 @@ internal sealed class AppItem
public bool IsPackaged { get; set; }
public List<IContextItem>? Commands { get; set; }
public string AppIdentifier { get; set; } = string.Empty;
public List<CommandContextItem>? Commands { get; set; }
public AppItem()
{

View File

@@ -5,7 +5,6 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.CmdPal.Ext.Apps.Commands;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Windows.Storage.Streams;
@@ -24,17 +23,14 @@ internal sealed partial class AppListItem : ListItem
public override IIconInfo? Icon { get => _icon.Value; set => base.Icon = value; }
public string AppIdentifier => _app.AppIdentifier;
public AppListItem(AppItem app, bool useThumbnails, bool isPinned)
public AppListItem(AppItem app, bool useThumbnails)
: base(new AppCommand(app))
{
_app = app;
Title = app.Name;
Subtitle = app.Subtitle;
Tags = [_appTag];
MoreCommands = AddPinCommands(_app.Commands!, isPinned);
MoreCommands = _app.Commands!.ToArray();
_details = new Lazy<Details>(() =>
{
@@ -125,37 +121,4 @@ internal sealed partial class AppListItem : ListItem
return icon;
}
private IContextItem[] AddPinCommands(List<IContextItem> commands, bool isPinned)
{
var newCommands = new List<IContextItem>();
newCommands.AddRange(commands);
newCommands.Add(new SeparatorContextItem());
// 0x50 = P
// Full key chord would be Ctrl+P
var pinKeyChord = KeyChordHelpers.FromModifiers(true, false, false, false, 0x50, 0);
if (isPinned)
{
newCommands.Add(
new CommandContextItem(
new UnpinAppCommand(this.AppIdentifier))
{
RequestedShortcut = pinKeyChord,
});
}
else
{
newCommands.Add(
new CommandContextItem(
new PinAppCommand(this.AppIdentifier))
{
RequestedShortcut = pinKeyChord,
});
}
return newCommands.ToArray();
}
}

View File

@@ -6,7 +6,6 @@ using System;
using System.Globalization;
using System.Text;
using ManagedCommon;
using Microsoft.CmdPal.Ext.Apps.Helpers;
using Microsoft.CmdPal.Ext.Apps.Properties;
using Microsoft.CommandPalette.Extensions.Toolkit;
@@ -14,12 +13,14 @@ namespace Microsoft.CmdPal.Ext.Apps.Commands;
internal sealed partial class CopyPathCommand : InvokableCommand
{
private static readonly IconInfo TheIcon = new("\ue8c8");
private readonly string _target;
public CopyPathCommand(string target)
{
Name = Resources.copy_path;
Icon = Icons.CopyIcon;
Icon = TheIcon;
_target = target;
}

View File

@@ -6,7 +6,6 @@ using System;
using System.Diagnostics;
using System.Threading.Tasks;
using ManagedCommon;
using Microsoft.CmdPal.Ext.Apps.Helpers;
using Microsoft.CmdPal.Ext.Apps.Properties;
using Microsoft.CommandPalette.Extensions.Toolkit;
@@ -14,12 +13,14 @@ namespace Microsoft.CmdPal.Ext.Apps.Commands;
internal sealed partial class OpenInConsoleCommand : InvokableCommand
{
private static readonly IconInfo TheIcon = new("\ue838");
private readonly string _target;
public OpenInConsoleCommand(string target)
{
Name = Resources.open_path_in_console;
Icon = Icons.OpenConsoleIcon;
Icon = TheIcon;
_target = target;
}

View File

@@ -4,7 +4,6 @@
using System.Diagnostics;
using System.Threading.Tasks;
using Microsoft.CmdPal.Ext.Apps.Helpers;
using Microsoft.CmdPal.Ext.Apps.Properties;
using Microsoft.CommandPalette.Extensions.Toolkit;
@@ -12,12 +11,14 @@ namespace Microsoft.CmdPal.Ext.Apps.Commands;
internal sealed partial class OpenPathCommand : InvokableCommand
{
private static readonly IconInfo TheIcon = new("\ue838");
private readonly string _target;
public OpenPathCommand(string target)
{
Name = Resources.open_location;
Icon = Icons.OpenPathIcon;
Icon = TheIcon;
_target = target;
}

View File

@@ -1,29 +0,0 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Diagnostics.CodeAnalysis;
using Microsoft.CmdPal.Ext.Apps.Helpers;
using Microsoft.CmdPal.Ext.Apps.Properties;
using Microsoft.CmdPal.Ext.Apps.State;
using Microsoft.CommandPalette.Extensions.Toolkit;
namespace Microsoft.CmdPal.Ext.Apps.Commands;
internal sealed partial class PinAppCommand : InvokableCommand
{
private readonly string _appIdentifier;
public PinAppCommand(string appIdentifier)
{
_appIdentifier = appIdentifier;
Name = Resources.pin_app;
Icon = Icons.PinIcon;
}
public override CommandResult Invoke()
{
PinnedAppsManager.Instance.PinApp(_appIdentifier);
return CommandResult.KeepOpen();
}
}

View File

@@ -5,7 +5,6 @@
using System;
using System.Diagnostics;
using System.Threading.Tasks;
using Microsoft.CmdPal.Ext.Apps.Helpers;
using Microsoft.CmdPal.Ext.Apps.Properties;
using Microsoft.CmdPal.Ext.Apps.Utils;
using Microsoft.CommandPalette.Extensions.Toolkit;
@@ -14,6 +13,8 @@ namespace Microsoft.CmdPal.Ext.Apps.Commands;
internal sealed partial class RunAsAdminCommand : InvokableCommand
{
private static readonly IconInfo TheIcon = new("\uE7EF");
private readonly string _target;
private readonly string _parentDir;
private readonly bool _packaged;
@@ -21,7 +22,7 @@ internal sealed partial class RunAsAdminCommand : InvokableCommand
public RunAsAdminCommand(string target, string parentDir, bool packaged)
{
Name = Resources.run_as_administrator;
Icon = Icons.RunAsIcon;
Icon = TheIcon;
_target = target;
_parentDir = parentDir;

View File

@@ -5,7 +5,6 @@
using System;
using System.Diagnostics;
using System.Threading.Tasks;
using Microsoft.CmdPal.Ext.Apps.Helpers;
using Microsoft.CmdPal.Ext.Apps.Properties;
using Microsoft.CmdPal.Ext.Apps.Utils;
using Microsoft.CommandPalette.Extensions.Toolkit;
@@ -14,19 +13,21 @@ namespace Microsoft.CmdPal.Ext.Apps.Commands;
internal sealed partial class RunAsUserCommand : InvokableCommand
{
private static readonly IconInfo TheIcon = new("\uE7EE");
private readonly string _target;
private readonly string _parentDir;
public RunAsUserCommand(string target, string parentDir)
{
Name = Resources.run_as_different_user;
Icon = Icons.RunAsUserIcon;
Icon = TheIcon;
_target = target;
_parentDir = parentDir;
}
internal static async Task RunAsUser(string target, string parentDir)
internal static async Task RunAsAdmin(string target, string parentDir)
{
await Task.Run(() =>
{
@@ -38,7 +39,7 @@ internal sealed partial class RunAsUserCommand : InvokableCommand
public override CommandResult Invoke()
{
_ = RunAsUser(_target, _parentDir).ConfigureAwait(false);
_ = RunAsAdmin(_target, _parentDir).ConfigureAwait(false);
return CommandResult.Dismiss();
}

View File

@@ -1,28 +0,0 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.CmdPal.Ext.Apps.Helpers;
using Microsoft.CmdPal.Ext.Apps.Properties;
using Microsoft.CmdPal.Ext.Apps.State;
using Microsoft.CommandPalette.Extensions.Toolkit;
namespace Microsoft.CmdPal.Ext.Apps.Commands;
internal sealed partial class UnpinAppCommand : InvokableCommand
{
private readonly string _appIdentifier;
public UnpinAppCommand(string appIdentifier)
{
_appIdentifier = appIdentifier;
Name = Resources.unpin_app;
Icon = Icons.UnpinIcon;
}
public override CommandResult Invoke()
{
PinnedAppsManager.Instance.UnpinApp(_appIdentifier);
return CommandResult.KeepOpen();
}
}

View File

@@ -1,26 +0,0 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.CommandPalette.Extensions.Toolkit;
namespace Microsoft.CmdPal.Ext.Apps.Helpers;
public static partial class Icons
{
public static IconInfo UnpinIcon { get; } = new("\uE77A");
public static IconInfo PinIcon { get; } = new("\uE840");
public static IconInfo RunAsIcon { get; } = new("\uE7EF");
public static IconInfo RunAsUserIcon { get; } = new("\uE7EE");
public static IconInfo CopyIcon { get; } = new("\ue8c8");
public static IconInfo OpenConsoleIcon { get; } = new("\ue838");
public static IconInfo OpenPathIcon { get; } = new("\ue838");
public static IconInfo AllAppsIcon { get; } = IconHelpers.FromRelativePath("Assets\\AllApps.svg");
}

View File

@@ -1,21 +0,0 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using Microsoft.CmdPal.Ext.Apps.State;
namespace Microsoft.CmdPal.Ext.Apps;
[JsonSerializable(typeof(string))]
[JsonSerializable(typeof(PinnedApps))]
[JsonSerializable(typeof(List<string>), TypeInfoPropertyName = "StringList")]
[JsonSourceGenerationOptions(UseStringEnumConverter = true, WriteIndented = true, IncludeFields = true, PropertyNameCaseInsensitive = true, AllowTrailingCommas = true)]
internal sealed partial class JsonSerializationContext : JsonSerializerContext
{
}

View File

@@ -10,9 +10,7 @@ using System.Xml;
using ManagedCommon;
using Microsoft.CmdPal.Ext.Apps.Commands;
using Microsoft.CmdPal.Ext.Apps.Properties;
using Microsoft.CmdPal.Ext.Apps.State;
using Microsoft.CmdPal.Ext.Apps.Utils;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Windows.System;
using Windows.Win32;
@@ -72,15 +70,9 @@ public class UWPApplication : IProgram
return Resources.packaged_application;
}
public string GetAppIdentifier()
public List<CommandContextItem> GetCommands()
{
// Use UserModelId for UWP apps as it's unique
return UserModelId;
}
public List<IContextItem> GetCommands()
{
List<IContextItem> commands = [];
List<CommandContextItem> commands = [];
if (CanRunElevated)
{
@@ -519,25 +511,6 @@ public class UWPApplication : IProgram
}
}
internal AppItem ToAppItem()
{
var app = this;
var iconPath = app.LogoType != LogoType.Error ? app.LogoPath : string.Empty;
var item = new AppItem()
{
Name = app.Name,
Subtitle = app.Description,
Type = UWPApplication.Type(),
IcoPath = iconPath,
DirPath = app.Location,
UserModelId = app.UserModelId,
IsPackaged = true,
Commands = app.GetCommands(),
AppIdentifier = app.GetAppIdentifier(),
};
return item;
}
/*
public ImageSource Logo()
{

View File

@@ -19,9 +19,7 @@ using System.Windows.Input;
using ManagedCommon;
using Microsoft.CmdPal.Ext.Apps.Commands;
using Microsoft.CmdPal.Ext.Apps.Properties;
using Microsoft.CmdPal.Ext.Apps.State;
using Microsoft.CmdPal.Ext.Apps.Utils;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Microsoft.Win32;
using Windows.System;
@@ -188,9 +186,9 @@ public class Win32Program : IProgram
return true;
}
public List<IContextItem> GetCommands()
public List<CommandContextItem> GetCommands()
{
List<IContextItem> commands = new List<IContextItem>();
List<CommandContextItem> commands = new List<CommandContextItem>();
if (AppType != ApplicationType.InternetShortcutApplication && AppType != ApplicationType.Folder && AppType != ApplicationType.GenericFile)
{
@@ -233,12 +231,6 @@ public class Win32Program : IProgram
return ExecutableName;
}
public string GetAppIdentifier()
{
// Use a combination of name and path to create a unique identifier
return $"{Name}|{FullPath}";
}
private static Win32Program CreateWin32Program(string path)
{
try
@@ -941,29 +933,4 @@ public class Win32Program : IProgram
return Array.Empty<Win32Program>();
}
}
internal AppItem ToAppItem()
{
var app = this;
var icoPath = string.IsNullOrEmpty(app.IcoPath) ?
(app.AppType == Win32Program.ApplicationType.InternetShortcutApplication ?
app.IcoPath :
app.FullPath) :
app.IcoPath;
icoPath = icoPath.EndsWith(".lnk", System.StringComparison.InvariantCultureIgnoreCase) ?
app.FullPath :
icoPath;
return new AppItem()
{
Name = app.Name,
Subtitle = app.Description,
Type = app.Type(),
IcoPath = icoPath,
ExePath = !string.IsNullOrEmpty(app.LnkFilePath) ? app.LnkFilePath : app.FullPath,
DirPath = app.Location,
Commands = app.GetCommands(),
AppIdentifier = app.GetAppIdentifier(),
};
}
}

View File

@@ -213,15 +213,6 @@ namespace Microsoft.CmdPal.Ext.Apps.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to Pin.
/// </summary>
internal static string pin_app {
get {
return ResourceManager.GetString("pin_app", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Run as administrator.
/// </summary>
@@ -276,15 +267,6 @@ namespace Microsoft.CmdPal.Ext.Apps.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to Unpin.
/// </summary>
internal static string unpin_app {
get {
return ResourceManager.GetString("unpin_app", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Experimental: When enabled, Command Palette will load thumbnails from the Windows Shell. Using thumbnails may cause the app to crash on launch.
/// </summary>

View File

@@ -199,10 +199,4 @@
<value>Experimental: When enabled, Command Palette will load thumbnails from the Windows Shell. Using thumbnails may cause the app to crash on launch</value>
<comment>A description for "use_thumbnails_setting_label"</comment>
</data>
<data name="pin_app" xml:space="preserve">
<value>Pin</value>
</data>
<data name="unpin_app" xml:space="preserve">
<value>Unpin</value>
</data>
</root>

View File

@@ -1,34 +0,0 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Microsoft.CmdPal.Ext.Apps.State;
public class PinStateChangedEventArgs : EventArgs
{
/// <summary>
/// Gets the identifier of the application whose pin state has changed.
/// </summary>
public string AppIdentifier { get; }
/// <summary>
/// Gets a value indicating whether the specified app identifier was pinned or not.
/// </summary>
public bool IsPinned { get; }
/// <summary>
/// Initializes a new instance of the <see cref="PinStateChangedEventArgs"/> class.
/// </summary>
/// <param name="appIdentifier">The identifier of the application whose pin state has changed.</param>
public PinStateChangedEventArgs(string appIdentifier, bool isPinned)
{
AppIdentifier = appIdentifier ?? throw new ArgumentNullException(nameof(appIdentifier));
IsPinned = isPinned;
}
}

View File

@@ -1,47 +0,0 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Text.Json;
namespace Microsoft.CmdPal.Ext.Apps.State;
public sealed class PinnedApps
{
public List<string> PinnedAppIdentifiers { get; set; } = [];
public static PinnedApps ReadFromFile(string path)
{
if (!File.Exists(path))
{
return new PinnedApps();
}
try
{
var jsonString = File.ReadAllText(path);
var result = JsonSerializer.Deserialize<PinnedApps>(jsonString, JsonSerializationContext.Default.PinnedApps);
return result ?? new PinnedApps();
}
catch
{
return new PinnedApps();
}
}
public static void WriteToFile(string path, PinnedApps data)
{
try
{
var jsonString = JsonSerializer.Serialize(data, JsonSerializationContext.Default.PinnedApps);
File.WriteAllText(path, jsonString);
}
catch
{
// Silently fail - we don't want pinning issues to crash the extension
}
}
}

View File

@@ -1,82 +0,0 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using ManagedCommon;
using Microsoft.CommandPalette.Extensions.Toolkit;
namespace Microsoft.CmdPal.Ext.Apps.State;
public sealed class PinnedAppsManager
{
private static readonly Lazy<PinnedAppsManager> _instance = new(() => new PinnedAppsManager());
private readonly string _pinnedAppsFilePath;
public static PinnedAppsManager Instance => _instance.Value;
private PinnedApps _pinnedApps = new();
// Add event for when pinning state changes
public event EventHandler<PinStateChangedEventArgs>? PinStateChanged;
private PinnedAppsManager()
{
_pinnedAppsFilePath = GetPinnedAppsFilePath();
LoadPinnedApps();
}
public bool IsAppPinned(string appIdentifier)
{
return _pinnedApps.PinnedAppIdentifiers.Contains(appIdentifier, StringComparer.OrdinalIgnoreCase);
}
public void PinApp(string appIdentifier)
{
if (!IsAppPinned(appIdentifier))
{
_pinnedApps.PinnedAppIdentifiers.Add(appIdentifier);
SavePinnedApps();
Logger.LogTrace($"Pinned app: {appIdentifier}");
PinStateChanged?.Invoke(this, new PinStateChangedEventArgs(appIdentifier, true));
}
}
public string[] GetPinnedAppIdentifiers()
{
return _pinnedApps.PinnedAppIdentifiers.ToArray();
}
public void UnpinApp(string appIdentifier)
{
var removed = _pinnedApps.PinnedAppIdentifiers.RemoveAll(id =>
string.Equals(id, appIdentifier, StringComparison.OrdinalIgnoreCase));
if (removed > 0)
{
SavePinnedApps();
Logger.LogTrace($"Unpinned app: {appIdentifier}");
PinStateChanged?.Invoke(this, new PinStateChangedEventArgs(appIdentifier, false));
}
}
private void LoadPinnedApps()
{
_pinnedApps = PinnedApps.ReadFromFile(_pinnedAppsFilePath);
}
private void SavePinnedApps()
{
PinnedApps.WriteToFile(_pinnedAppsFilePath, _pinnedApps);
}
private static string GetPinnedAppsFilePath()
{
var directory = Utilities.BaseSettingsPath("Microsoft.CmdPal");
Directory.CreateDirectory(directory);
return Path.Combine(directory, "apps.pinned.json");
}
}