Merge branch 'dev/migrie/40113/new-core-project' into dev/migrie/40113/extension-hosts-try-2

This commit is contained in:
Mike Griese
2025-07-15 09:53:07 -05:00
49 changed files with 689 additions and 157 deletions

View File

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

View File

@@ -38,6 +38,11 @@ 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:
@@ -95,7 +100,7 @@ extends:
useManagedIdentity: $(SigningUseManagedIdentity)
clientId: $(SigningOriginalClientId)
# Have msbuild use the release nuget config profile
additionalBuildOptions: /p:RestoreConfigFile="$(Build.SourcesDirectory)\.pipelines\release-nuget.config"
additionalBuildOptions: /p:RestoreConfigFile="$(Build.SourcesDirectory)\.pipelines\release-nuget.config" /p:EnableCmdPalAOT=${{ parameters.enableAOT }}
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*") {
if ($depsJsonFullFileName -like "*CmdPal*" -or $depsJsonFullFileName -like "*CommandPalette*") {
return
}

View File

@@ -21,6 +21,7 @@
<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,6 +1497,7 @@ 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
@@ -1579,4 +1580,3 @@ SOFTWARE.
- WinUIEx 2.2.0
- WPF-UI 3.0.5
- WyHash 1.0.5

View File

@@ -47,6 +47,8 @@ Contact the developers of a plugin directly for assistance with a specific plugi
| [Weather](https://github.com/ruslanlap/PowerToysRun-Weather) | [ruslanlap](https://github.com/ruslanlap) | Get real-time weather information directly from PowerToys Run. |
| [Pomodoro](https://github.com/ruslanlap/PowerToysRun-Pomodoro) | [ruslanlap](https://github.com/ruslanlap) | Manage Pomodoro productivity sessions directly from PowerToys Run. |
| [Definition](https://github.com/ruslanlap/PowerToysRun-Definition) | [ruslanlap](https://github.com/ruslanlap) | Lookup word definitions, phonetics, and synonyms directly in PowerToys Run. |
| [Hotkeys](https://github.com/ruslanlap/PowerToysRun-Hotkeys) | [ruslanlap](https://github.com/ruslanlap) | Create, manage, and trigger custom keyboard shortcuts directly from PowerToys Run. |
| [RandomGen](https://github.com/ruslanlap/PowerToysRun-RandomGen) | [ruslanlap](https://github.com/ruslanlap) | 🎲 Generate random data instantly with a single keystroke. Perfect for developers, testers, designers, and anyone who needs quick access to random data. Features include secure passwords, PINs, names, business data, dates, numbers, GUIDs, color codes, and more. Especially useful for designers who need random color codes and placeholder content. |
## Extending software plugins

View File

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

View File

@@ -7,6 +7,7 @@
<CsWinRTAotWarningLevel>2</CsWinRTAotWarningLevel>
<!-- Suppress DynamicallyAccessedMemberTypes.PublicParameterlessConstructor in fallback code path of Windows SDK projection -->
<WarningsNotAsErrors>IL2081;CsWinRT1028;$(WarningsNotAsErrors)</WarningsNotAsErrors>
<!-- 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>
</PropertyGroup>
</Project>

View File

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

View File

@@ -162,10 +162,6 @@ public sealed partial class SearchBar : UserControl,
CurrentPageViewModel.Filter = FilterBox.Text;
}
}
else if (e.Key == VirtualKey.Left && altPressed)
{
WeakReferenceMessenger.Default.Send<NavigateBackMessage>(new());
}
if (!e.Handled)
{

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>
<tk7controls:MarkdownTextBlock
<labToolkit: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>
<tk7controls:MarkdownTextBlock
<labToolkit:MarkdownTextBlock
Grid.Column="1"
VerticalAlignment="Center"
Background="Transparent"

View File

@@ -10,7 +10,9 @@
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"
@@ -20,6 +22,12 @@
<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"
@@ -44,12 +52,9 @@
<DataTemplate x:Key="MarkdownContentTemplate" x:DataType="viewModels:ContentMarkdownViewModel">
<Grid Margin="0,4,4,4" Padding="12,8,8,8">
<toolkit:MarkdownTextBlock
<labToolkit:MarkdownTextBlock
Background="Transparent"
Header3FontSize="12"
Header3FontWeight="Normal"
Header3Foreground="{ThemeResource TextFillColorSecondaryBrush}"
IsTextSelectionEnabled="True"
Config="{StaticResource DefaultMarkdownConfig}"
Text="{x:Bind Body, Mode=OneWay}" />
</Grid>
</DataTemplate>
@@ -62,12 +67,9 @@
<DataTemplate x:Key="NestedMarkdownContentTemplate" x:DataType="viewModels:ContentMarkdownViewModel">
<Grid>
<toolkit:MarkdownTextBlock
<labToolkit:MarkdownTextBlock
Background="Transparent"
Header3FontSize="12"
Header3FontWeight="Normal"
Header3Foreground="{ThemeResource TextFillColorSecondaryBrush}"
IsTextSelectionEnabled="True"
Config="{StaticResource DefaultMarkdownConfig}"
Text="{x:Bind Body, Mode=OneWay}" />
</Grid>
</DataTemplate>

View File

@@ -108,7 +108,7 @@ public sealed partial class MainWindow : WindowEx,
App.Current.Services.GetService<SettingsModel>()!.SettingsChanged += SettingsChangedHandler;
// Make sure that we update the acrylic theme when the OS theme changes
RootShellPage.ActualThemeChanged += (s, e) => UpdateAcrylic();
RootShellPage.ActualThemeChanged += (s, e) => DispatcherQueue.TryEnqueue(UpdateAcrylic);
// Hardcoding event name to avoid bringing in the PowerToys.interop dependency. Event name must match CMDPAL_SHOW_EVENT from shared_constants.h
NativeEventWaiter.WaitForEventLoop("Local\\PowerToysCmdPal-ShowEvent-62336fcd-8611-4023-9b30-091a6af4cc5a", () =>
@@ -176,6 +176,8 @@ public sealed partial class MainWindow : WindowEx,
private void UpdateAcrylic()
{
_acrylicController?.RemoveAllSystemBackdropTargets();
_acrylicController = GetAcrylicConfig(Content);
// Enable the system backdrop.

View File

@@ -1,5 +1,6 @@
<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" />
@@ -23,6 +24,13 @@
<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>
@@ -71,7 +79,7 @@
<PackageReference Include="CommunityToolkit.WinUI.Converters" />
<PackageReference Include="CommunityToolkit.WinUI.Animations" />
<PackageReference Include="CommunityToolkit.WinUI.Extensions" />
<PackageReference Include="CommunityToolkit.WinUI.UI.Controls.Markdown" />
<PackageReference Include="CommunityToolkit.Labs.WinUI.Controls.MarkdownTextBlock" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" />
<PackageReference Include="Microsoft.WindowsAppSDK" />

View File

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

View File

@@ -9,8 +9,10 @@
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.UI.Controls"
xmlns:toolkit="using:CommunityToolkit.WinUI.Controls"
xmlns:ui="using:CommunityToolkit.WinUI"
xmlns:viewModels="using:Microsoft.CmdPal.UI.ViewModels"
xmlns:coreViewModels="using:Microsoft.CmdPal.Core.ViewModels"
@@ -145,6 +147,11 @@
</ItemsControl>
</StackPanel>
</DataTemplate>
<markdownTextBlockRns:MarkdownThemes
x:Key="DefaultMarkdownThemeConfig"
H3FontSize="12"
H3FontWeight="Normal" />
<labToolkit:MarkdownConfig x:Key="DefaultMarkdownConfig" Themes="{StaticResource DefaultMarkdownThemeConfig}" />
</ResourceDictionary>
</Page.Resources>
@@ -407,14 +414,11 @@
TextWrapping="WrapWholeWords"
Visibility="{x:Bind ViewModel.Details.Title, Converter={StaticResource StringNotEmptyToVisibilityConverter}, Mode=OneWay}" />
<toolkit:MarkdownTextBlock
<labToolkit:MarkdownTextBlock
Grid.Row="2"
Margin="0,4,0,24"
Background="Transparent"
Header3FontSize="12"
Header3FontWeight="Normal"
Header3Foreground="{ThemeResource TextFillColorSecondaryBrush}"
IsTextSelectionEnabled="True"
Config="{StaticResource DefaultMarkdownConfig}"
Text="{x:Bind ViewModel.Details.Body, Mode=OneWay}" />
<ItemsRepeater

View File

@@ -21,6 +21,7 @@ using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media.Animation;
using DispatcherQueue = Microsoft.UI.Dispatching.DispatcherQueue;
using VirtualKey = Windows.System.VirtualKey;
namespace Microsoft.CmdPal.UI.Pages;
@@ -82,6 +83,7 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
WeakReferenceMessenger.Default.Register<ShowToastMessage>(this);
WeakReferenceMessenger.Default.Register<NavigateToPageMessage>(this);
AddHandler(PreviewKeyDownEvent, new KeyEventHandler(ShellPage_OnPreviewKeyDown), true);
AddHandler(PointerPressedEvent, new PointerEventHandler(ShellPage_OnPointerPressed), true);
RootFrame.Navigate(typeof(LoadingPage), ViewModel);
@@ -449,6 +451,14 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
}
}
private void ShellPage_OnPreviewKeyDown(object sender, KeyRoutedEventArgs e)
{
if (e.Key == VirtualKey.Left && e.KeyStatus.IsMenuKeyDown)
{
WeakReferenceMessenger.Default.Send<NavigateBackMessage>(new());
}
}
private void ShellPage_OnPointerPressed(object sender, PointerRoutedEventArgs e)
{
try

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="'$(Configuration)' == 'Debug'">False</PublishTrimmed>
<PublishTrimmed Condition="'$(Configuration)' != 'Debug'">False</PublishTrimmed>
<PublishTrimmed Condition="'$(EnableCmdPalAOT)' == 'true'">True</PublishTrimmed>
<PublishTrimmed Condition="'$(EnableCmdPalAOT)' != 'true'">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="'$(Configuration)' == 'Debug'">False</PublishTrimmed>
<PublishTrimmed Condition="'$(Configuration)' != 'Debug'">False</PublishTrimmed>
<PublishTrimmed Condition="'$(EnableCmdPalAOT)' == 'true'">True</PublishTrimmed>
<PublishTrimmed Condition="'$(EnableCmdPalAOT)' != 'true'">False</PublishTrimmed>
</PropertyGroup>
</Project>

View File

@@ -2,8 +2,12 @@
// 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;
@@ -29,9 +33,12 @@ 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];
public override ICommandItem[] TopLevelCommands() => [_listItem, ..Page.GetPinnedApps()];
public ICommandItem? LookupApp(string displayName)
{
@@ -62,4 +69,9 @@ public partial class AllAppsCommandProvider : CommandProvider
return null;
}
private void OnPinStateChanged(object? sender, System.EventArgs e)
{
RaiseItemsChanged(0);
}
}

View File

@@ -2,14 +2,20 @@
// 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;
@@ -18,16 +24,22 @@ namespace Microsoft.CmdPal.Ext.Apps;
public sealed partial class AllAppsPage : ListPage
{
private readonly Lock _listLock = new();
private AppListItem[] allAppsSection = [];
private AppItem[] allApps = [];
private AppListItem[] unpinnedApps = [];
private AppListItem[] pinnedApps = [];
public AllAppsPage()
{
this.Name = Resources.all_apps;
this.Icon = IconHelpers.FromRelativePath("Assets\\AllApps.svg");
this.Icon = Icons.AllAppsIcon;
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)
@@ -37,89 +49,139 @@ public sealed partial class AllAppsPage : ListPage
});
}
internal AppListItem[] GetPinnedApps()
{
BuildListItems();
return pinnedApps;
}
public override IListItem[] GetItems()
{
if (allAppsSection.Length == 0 || AppCache.Instance.Value.ShouldReload())
{
lock (_listLock)
{
BuildListItems();
}
}
return allAppsSection;
// Build or update the list if needed
BuildListItems();
return pinnedApps.Concat(unpinnedApps).ToArray();
}
private void BuildListItems()
{
this.IsLoading = true;
if (allApps.Length == 0 || AppCache.Instance.Value.ShouldReload())
{
lock (_listLock)
{
this.IsLoading = true;
Stopwatch stopwatch = new();
stopwatch.Start();
Stopwatch stopwatch = new();
stopwatch.Start();
var apps = GetPrograms();
var apps = GetPrograms();
this.allApps = apps.AllApps;
this.pinnedApps = apps.PinnedItems;
this.unpinnedApps = apps.UnpinnedItems;
this.allAppsSection = apps
.Select((app) => new AppListItem(app, true))
.ToArray();
this.IsLoading = false;
this.IsLoading = false;
AppCache.Instance.Value.ResetReloadFlag();
AppCache.Instance.Value.ResetReloadFlag();
stopwatch.Stop();
Logger.LogTrace($"{nameof(AllAppsPage)}.{nameof(BuildListItems)} took: {stopwatch.ElapsedMilliseconds} ms");
stopwatch.Stop();
Logger.LogTrace($"{nameof(AllAppsPage)}.{nameof(BuildListItems)} took: {stopwatch.ElapsedMilliseconds} ms");
}
}
}
internal List<AppItem> GetPrograms()
private AppItem[] GetAllApps()
{
var uwpResults = AppCache.Instance.Value.UWPs
.Where((application) => application.Enabled)
.Select(UwpToAppItem);
.Where((application) => application.Enabled)
.Select(app => app.ToAppItem());
var win32Results = AppCache.Instance.Value.Win32s
.Where((application) => application.Enabled && application.Valid)
.Select(app =>
{
var icoPath = string.IsNullOrEmpty(app.IcoPath) ?
(app.AppType == Win32Program.ApplicationType.InternetShortcutApplication ?
app.IcoPath :
app.FullPath) :
app.IcoPath;
.Select(app => app.ToAppItem());
// 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();
var allApps = uwpResults.Concat(win32Results).ToArray();
return allApps;
}
private AppItem UwpToAppItem(UWPApplication app)
internal (AppItem[] AllApps, AppListItem[] PinnedItems, AppListItem[] UnpinnedItems) GetPrograms()
{
var iconPath = app.LogoType != LogoType.Error ? app.LogoPath : string.Empty;
var item = new AppItem()
var allApps = GetAllApps();
var pinned = new List<AppListItem>();
var unpinned = new List<AppListItem>();
foreach (var app in allApps)
{
Name = app.Name,
Subtitle = app.Description,
Type = UWPApplication.Type(),
IcoPath = iconPath,
DirPath = app.Location,
UserModelId = app.UserModelId,
IsPackaged = true,
Commands = app.GetCommands(),
};
return item;
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);
}
}
}

View File

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

View File

@@ -5,6 +5,7 @@
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;
@@ -23,14 +24,17 @@ internal sealed partial class AppListItem : ListItem
public override IIconInfo? Icon { get => _icon.Value; set => base.Icon = value; }
public AppListItem(AppItem app, bool useThumbnails)
public string AppIdentifier => _app.AppIdentifier;
public AppListItem(AppItem app, bool useThumbnails, bool isPinned)
: base(new AppCommand(app))
{
_app = app;
Title = app.Name;
Subtitle = app.Subtitle;
Tags = [_appTag];
MoreCommands = _app.Commands!.ToArray();
MoreCommands = AddPinCommands(_app.Commands!, isPinned);
_details = new Lazy<Details>(() =>
{
@@ -121,4 +125,37 @@ 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,6 +6,7 @@ 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;
@@ -13,14 +14,12 @@ 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 = TheIcon;
Icon = Icons.CopyIcon;
_target = target;
}

View File

@@ -6,6 +6,7 @@ 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;
@@ -13,14 +14,12 @@ 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 = TheIcon;
Icon = Icons.OpenConsoleIcon;
_target = target;
}

View File

@@ -4,6 +4,7 @@
using System.Diagnostics;
using System.Threading.Tasks;
using Microsoft.CmdPal.Ext.Apps.Helpers;
using Microsoft.CmdPal.Ext.Apps.Properties;
using Microsoft.CommandPalette.Extensions.Toolkit;
@@ -11,14 +12,12 @@ 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 = TheIcon;
Icon = Icons.OpenPathIcon;
_target = target;
}

View File

@@ -0,0 +1,29 @@
// 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,6 +5,7 @@
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;
@@ -13,8 +14,6 @@ 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;
@@ -22,7 +21,7 @@ internal sealed partial class RunAsAdminCommand : InvokableCommand
public RunAsAdminCommand(string target, string parentDir, bool packaged)
{
Name = Resources.run_as_administrator;
Icon = TheIcon;
Icon = Icons.RunAsIcon;
_target = target;
_parentDir = parentDir;

View File

@@ -5,6 +5,7 @@
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;
@@ -13,21 +14,19 @@ 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 = TheIcon;
Icon = Icons.RunAsUserIcon;
_target = target;
_parentDir = parentDir;
}
internal static async Task RunAsAdmin(string target, string parentDir)
internal static async Task RunAsUser(string target, string parentDir)
{
await Task.Run(() =>
{
@@ -39,7 +38,7 @@ internal sealed partial class RunAsUserCommand : InvokableCommand
public override CommandResult Invoke()
{
_ = RunAsAdmin(_target, _parentDir).ConfigureAwait(false);
_ = RunAsUser(_target, _parentDir).ConfigureAwait(false);
return CommandResult.Dismiss();
}

View File

@@ -0,0 +1,28 @@
// 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

@@ -0,0 +1,26 @@
// 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

@@ -0,0 +1,21 @@
// 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,7 +10,9 @@ 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;
@@ -70,9 +72,15 @@ public class UWPApplication : IProgram
return Resources.packaged_application;
}
public List<CommandContextItem> GetCommands()
public string GetAppIdentifier()
{
List<CommandContextItem> commands = [];
// Use UserModelId for UWP apps as it's unique
return UserModelId;
}
public List<IContextItem> GetCommands()
{
List<IContextItem> commands = [];
if (CanRunElevated)
{
@@ -511,6 +519,25 @@ 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,7 +19,9 @@ 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;
@@ -186,9 +188,9 @@ public class Win32Program : IProgram
return true;
}
public List<CommandContextItem> GetCommands()
public List<IContextItem> GetCommands()
{
List<CommandContextItem> commands = new List<CommandContextItem>();
List<IContextItem> commands = new List<IContextItem>();
if (AppType != ApplicationType.InternetShortcutApplication && AppType != ApplicationType.Folder && AppType != ApplicationType.GenericFile)
{
@@ -231,6 +233,12 @@ 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
@@ -933,4 +941,29 @@ 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,6 +213,15 @@ 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>
@@ -267,6 +276,15 @@ 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,4 +199,10 @@
<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

@@ -0,0 +1,34 @@
// 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

@@ -0,0 +1,47 @@
// 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

@@ -0,0 +1,82 @@
// 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");
}
}

View File

@@ -32,6 +32,8 @@ internal sealed partial class FallbackOpenURLItem : FallbackCommandItem
{
if (!IsValidUrl(query))
{
_executeItem.Url = string.Empty;
_executeItem.Name = string.Empty;
Title = string.Empty;
Subtitle = string.Empty;
return;

View File

@@ -23,28 +23,31 @@ internal sealed partial class WebSearchListPage : DynamicListPage
private readonly SettingsManager _settingsManager;
private static readonly CompositeFormat PluginInBrowserName = System.Text.CompositeFormat.Parse(Properties.Resources.plugin_in_browser_name);
private static readonly CompositeFormat PluginOpen = System.Text.CompositeFormat.Parse(Properties.Resources.plugin_open);
private List<ListItem> allItems;
private List<ListItem> _allItems;
public WebSearchListPage(SettingsManager settingsManager)
{
Name = Resources.command_item_title;
Title = Resources.command_item_title;
PlaceholderText = Resources.plugin_description;
Icon = IconHelpers.FromRelativePath("Assets\\WebSearch.png");
allItems = [new(new NoOpCommand())
{
Icon = IconHelpers.FromRelativePath("Assets\\WebSearch.png"),
Title = Properties.Resources.plugin_description,
Subtitle = string.Format(CultureInfo.CurrentCulture, PluginOpen, BrowserInfo.Name ?? BrowserInfo.MSEdgeName),
}
];
_allItems = [];
Id = "com.microsoft.cmdpal.websearch";
_settingsManager = settingsManager;
_historyItems = _settingsManager.ShowHistory != Resources.history_none ? _settingsManager.LoadHistory() : null;
if (_historyItems != null)
{
allItems.AddRange(_historyItems);
_allItems.AddRange(_historyItems);
}
// It just looks viewer to have string twice on the page, and default placeholder is good enough
PlaceholderText = _allItems.Count > 0 ? Resources.plugin_description : string.Empty;
EmptyContent = new CommandItem(new NoOpCommand())
{
Icon = Icon,
Title = Properties.Resources.plugin_description,
Subtitle = string.Format(CultureInfo.CurrentCulture, PluginInBrowserName, BrowserInfo.Name ?? BrowserInfo.MSEdgeName),
};
}
public List<ListItem> Query(string query)
@@ -59,17 +62,7 @@ internal sealed partial class WebSearchListPage : DynamicListPage
var results = new List<ListItem>();
// empty query
if (string.IsNullOrEmpty(query))
{
results.Add(new ListItem(new SearchWebCommand(string.Empty, _settingsManager))
{
Title = Properties.Resources.plugin_description,
Subtitle = string.Format(CultureInfo.CurrentCulture, PluginInBrowserName, BrowserInfo.Name ?? BrowserInfo.MSEdgeName),
Icon = new IconInfo(_iconPath),
});
}
else
if (!string.IsNullOrEmpty(query))
{
var searchTerm = query;
var result = new ListItem(new SearchWebCommand(searchTerm, _settingsManager))
@@ -91,9 +84,9 @@ internal sealed partial class WebSearchListPage : DynamicListPage
public override void UpdateSearchText(string oldSearch, string newSearch)
{
allItems = [.. Query(newSearch)];
_allItems = [.. Query(newSearch)];
RaiseItemsChanged(0);
}
public override IListItem[] GetItems() => [.. allItems];
public override IListItem[] GetItems() => [.. _allItems];
}

View File

@@ -90,14 +90,12 @@ namespace Peek.FilePreviewer.Previewers
unsafe
{
// This runs the preview handler in a separate process (prevhost.exe)
// TODO: Figure out how to get it to run in a low integrity level
if (!HandlerFactories.TryGetValue(clsid, out var factory))
{
var hr = PInvoke_FilePreviewer.CoGetClassObject(clsid, CLSCTX.CLSCTX_LOCAL_SERVER, null, typeof(IClassFactory).GUID, out object pFactory);
Marshal.ThrowExceptionForHR(hr);
// Storing the factory in memory helps makes the handlers load faster
// TODO: Maybe free them after some inactivity or when Peek quits?
factory = (IClassFactory)pFactory;
factory.LockServer(true);
HandlerFactories.AddOrUpdate(clsid, factory, (_, _) => factory);
@@ -213,6 +211,20 @@ namespace Peek.FilePreviewer.Previewers
return !string.IsNullOrEmpty(GetPreviewHandlerGuid(item.Extension));
}
public static void ReleaseHandlerFactories()
{
foreach (var factory in HandlerFactories.Values)
{
try
{
Marshal.FinalReleaseComObject(factory);
}
catch
{
}
}
}
private static string? GetPreviewHandlerGuid(string fileExt)
{
const string PreviewHandlerKeyPath = "shellex\\{8895b1c6-b41f-4c1c-a562-0d564250836f}";

View File

@@ -12,6 +12,7 @@ using Microsoft.UI.Xaml;
using Peek.Common;
using Peek.FilePreviewer;
using Peek.FilePreviewer.Models;
using Peek.FilePreviewer.Previewers;
using Peek.UI.Native;
using Peek.UI.Telemetry.Events;
using Peek.UI.Views;
@@ -111,6 +112,7 @@ namespace Peek.UI
NativeEventWaiter.WaitForEventLoop(Constants.ShowPeekEvent(), OnPeekHotkey);
NativeEventWaiter.WaitForEventLoop(Constants.TerminatePeekEvent(), () =>
{
ShellPreviewHandlerPreviewer.ReleaseHandlerFactories();
EtwTrace?.Dispose();
Environment.Exit(0);
});

View File

@@ -15,6 +15,7 @@ using Microsoft.UI.Xaml.Input;
using Peek.Common.Constants;
using Peek.Common.Extensions;
using Peek.FilePreviewer.Models;
using Peek.FilePreviewer.Previewers;
using Peek.UI.Extensions;
using Peek.UI.Helpers;
using Peek.UI.Telemetry.Events;
@@ -204,6 +205,8 @@ namespace Peek.UI
ViewModel.ScalingFactor = 1;
this.Content.KeyUp -= Content_KeyUp;
ShellPreviewHandlerPreviewer.ReleaseHandlerFactories();
}
/// <summary>

View File

@@ -405,13 +405,19 @@ public:
{
ResetEvent(m_hInvokeEvent);
SetEvent(m_hTerminateEvent);
WaitForSingleObject(m_hProcess, 1500);
auto result = TerminateProcess(m_hProcess, 1);
if (result == 0)
HANDLE hProcess = OpenProcess(SYNCHRONIZE | PROCESS_TERMINATE, FALSE, m_processPid);
if (WaitForSingleObject(hProcess, 1500) == WAIT_TIMEOUT)
{
int error = GetLastError();
Logger::trace("Couldn't terminate the process. Last error: {}", error);
auto result = TerminateProcess(hProcess, 1);
if (result == 0)
{
int error = GetLastError();
Logger::trace("Couldn't terminate the process. Last error: {}", error);
}
}
CloseHandle(hProcess);
CloseHandle(m_hProcess);
m_hProcess = 0;
m_processPid = 0;

View File

@@ -51,6 +51,7 @@ namespace PowerAccent.Core
SR_CYRL,
SV,
TK,
VI,
}
internal sealed class Languages
@@ -113,6 +114,7 @@ namespace PowerAccent.Core
Language.SR_CYRL => GetDefaultLetterKeySRCyrillic(letter), // Serbian Cyrillic
Language.SV => GetDefaultLetterKeySV(letter), // Swedish
Language.TK => GetDefaultLetterKeyTK(letter), // Turkish
Language.VI => GetDefaultLetterKeyVI(letter), // Vietnamese
_ => throw new ArgumentException("The language {0} is not known in this context", lang.ToString()),
});
}
@@ -168,6 +170,7 @@ namespace PowerAccent.Core
.Union(GetDefaultLetterKeySRCyrillic(letter))
.Union(GetDefaultLetterKeySV(letter))
.Union(GetDefaultLetterKeyTK(letter))
.Union(GetDefaultLetterKeyVI(letter))
.Union(GetDefaultLetterKeySPECIAL(letter))
.ToArray();
@@ -890,6 +893,22 @@ namespace PowerAccent.Core
};
}
// Vietnamese
private static string[] GetDefaultLetterKeyVI(LetterKey letter)
{
return letter switch
{
LetterKey.VK_A => new[] { "à", "ả", "ã", "á", "ạ", "ă", "ằ", "ẳ", "ẵ", "ắ", "ặ", "â", "ầ", "ẩ", "ẫ", "ấ", "ậ" },
LetterKey.VK_D => new[] { "đ" },
LetterKey.VK_E => new[] { "è", "ẻ", "ẽ", "é", "ẹ", "ê", "ề", "ể", "ễ", "ế", "ệ" },
LetterKey.VK_I => new[] { "ì", "ỉ", "ĩ", "í", "ị" },
LetterKey.VK_O => new[] { "ò", "ỏ", "õ", "ó", "ọ", "ô", "ồ", "ổ", "ỗ", "ố", "ộ", "ơ", "ờ", "ở", "ỡ", "ớ", "ợ" },
LetterKey.VK_U => new[] { "ù", "ủ", "ũ", "ú", "ụ", "ư", "ừ", "ử", "ữ", "ứ", "ự" },
LetterKey.VK_Y => new[] { "ỳ", "ỷ", "ỹ", "ý", "ỵ" },
_ => Array.Empty<string>(),
};
}
// IPA (International Phonetic Alphabet)
private static string[] GetDefaultLetterKeyIPA(LetterKey letter)
{

View File

@@ -3566,6 +3566,9 @@ Activate by holding the key for the character you want to add an accent to, then
<data name="QuickAccent_SelectedLanguage_Turkish" xml:space="preserve">
<value>Turkish</value>
</data>
<data name="QuickAccent_SelectedLanguage_Vietnamese" xml:space="preserve">
<value>Vietnamese</value>
</data>
<data name="QuickAccent_SelectedLanguage_Icelandic" xml:space="preserve">
<value>Icelandic</value>
</data>

View File

@@ -67,6 +67,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
new PowerAccentLanguageModel("SR_CYRL", "QuickAccent_SelectedLanguage_Serbian_Cyrillic", LanguageGroup),
new PowerAccentLanguageModel("SV", "QuickAccent_SelectedLanguage_Swedish", LanguageGroup),
new PowerAccentLanguageModel("TK", "QuickAccent_SelectedLanguage_Turkish", LanguageGroup),
new PowerAccentLanguageModel("VI", "QuickAccent_SelectedLanguage_Vietnamese", LanguageGroup),
new PowerAccentLanguageModel("CY", "QuickAccent_SelectedLanguage_Welsh", LanguageGroup),
];