Compare commits

..

15 Commits

Author SHA1 Message Date
Gleb Khmyznikov
47da962dc1 Fix UITests link in devdocs 2025-07-15 16:38:37 +02:00
Mike Griese
53bb471449 CmdPal: Add a viewmodel factory for pages (#40504)
_targets #40482_
ref #40113

A smaller refactor, to be sure.

This just moves the instantiation of PageViewModel objects out of the
ShellViewModel, and into its own class.

The idea being that other page types could be added, just by extending
that factory (or implementing your own), and then also handling those
new VMs in your ShellPage.xaml.cs equivalent.
2025-07-15 09:33:28 -05:00
Yu Leng
0783763dd0 [AOT] Enable AOT for CmdPal (#40551)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request

Base on https://github.com/microsoft/PowerToys/pull/40486 we can easily
use lab package. So all blocker has been resolved.

1. Replace CommunityToolkit.WinUI.UI.Controls.Markdown with
CommunityToolkit.Labs.WinUI.Controls.MarkdownTextBlock
2. Add default markdown style config to align some configuration with
the original one. (but still have some gap)
3. Add new configuration in pipeline to control the AOT enable/disable.

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

- [x] **Closes:** #38279
- [x] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [x] **Tests:** Added/updated and all pass
- [x] **Localization:** All end-user-facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed

---------

Co-authored-by: Yu Leng (from Dev Box) <yuleng@microsoft.com>
Co-authored-by: Mike Griese <migrie@microsoft.com>
2025-07-15 22:15:18 +08:00
Michael Jolley
19390e3198 Users can now pin/unpin apps from the top of AllApps extension (#40544)
Users can now use Ctrl+P to toggle pinning and unpinning applications
from the top of the All Apps extension.

This pinned status persists through restarts & reboots.


https://github.com/user-attachments/assets/86895a38-7312-438a-9409-b50a85979d12

Reference: #40543

---------

Co-authored-by: Mike Griese <migrie@microsoft.com>
2025-07-15 08:59:32 -05:00
Jiří Polášek
5800b81638 Fix loading top-level commands (#40602)
## Summary of the Pull Request
Updates LoadTopLevelCommandsFromProvider so it returns list of top-level
commands for further instead of modifying TopLevelCommands collection
directly.

Reverts unintended change from cfa5f75 where
LoadTopLevelCommandsFromProvider updates the shared TopLevelCommands
collection directly from in a task, causing thread-safety issues and
bypassing synchronization lock on the collection.

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

- [ ] **Closes:** --
- [ ] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [ ] **Tests:** Added/updated and all pass
- [ ] **Localization:** All end-user-facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2025-07-14 20:03:52 -05:00
Mike Griese
d96c29d22d Replace our nuget.org feed with a public azure artifacts feed (#40486)
_This madness has gone on too long, I say_

This replaces our default nuget.org feed with a public azure artifacts
feed in the shine-oss org. This is what literally everyone else does, I
don't know why we don't.

This should unblock _wait where'd that issue go_ since we can just add
the community toolkit labs feed as an upstream

This has the negative side effect that it did prompt me to log in to
azure artifacts with my MSA. I've cancelled like 5 prompts now, but it
seems to still be working on it?
2025-07-14 13:58:59 -05:00
Davide Giacometti
227c5d8147 [Peek] Terminate Preview Handlers Processes (#40116)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request

This is an attempt to release Preview Handlers instantiated by Peek and
close the related processe.

⚠️ Note that even if the PR improve the current behavior, the solution
doesn't work 100% of times.
I noticed that sometimes the process gets leaked also when Preview
Handler is used in Explorer 🤔

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

- [x] **Closes:** #40117
- [ ] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [ ] **Tests:** Added/updated and all pass
- [ ] **Localization:** All end-user-facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

- Ported the same fix applied to CmdPal to ensure the process is
terminated gracefully instead of being killed:
https://github.com/microsoft/PowerToys/pull/39589
- Attempt to cleanup Preview Handlers and close the relative process

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed

Tested manually:
- Preview through some Excel and Word files
- Close Peek window
- Excel and Word processes are closed
2025-07-14 08:50:45 +08:00
Jiří Polášek
d258dcd61b CmdPal: Remove backdrop targets from existing backdrop controller before assigning a new one (#40540)
## Summary of the Pull Request
Also runs the code through UI thread. it will ensure correct access to
_acrylicController, and also because the method touches UI element.


<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

- [x] **Closes:** #38659 
- [ ] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [x] **Tests:** Added/updated and all pass
- [x] **Localization:** All end-user-facing strings can be localized
- [x] **Dev docs:** Added/updated
- [x] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [x] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2025-07-12 14:38:01 -05:00
Jiří Polášek
1d464cc307 Use Empty content for empty Web search page (#40549)
## Summary of the Pull Request
Display full page message when the Web Search extension page is empty

<img width="786" height="473" alt="image"
src="https://github.com/user-attachments/assets/2d08d809-1127-44b3-9842-50969eb6bef7"
/>
<img width="786" height="473" alt="image"
src="https://github.com/user-attachments/assets/155374cc-3e13-4cc0-b7e5-b4fa2b371ba7"
/>

## PR Checklist

- [x] **Closes:** #38969 
- [x] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [x] **Tests:** yay
- [x] **Localization:** nope
- [x] **Dev docs:** none
- [x] **New binaries:** zilch
- [x] **Documentation updated:** no need

## Detailed Description of the Pull Request / Additional comments

--

## Validation Steps Performed

Tested with and without enabled history.
2025-07-11 21:11:13 -05:00
ruslanlap
3686d6ac19 Add PowerToysRun-Hotkeys plugin to third-party plugins documentation (#40530)
This PR adds the Hotkeys and RandomGen plugins to the third-party
plugins documentation in the General plugins section.

## Hotkeys Plugin
⌨️ Hotkeys for PowerToys Run - Create, manage, and trigger custom
keyboard shortcuts directly from PowerToys Run

![Hotkeys
Demo](https://github.com/ruslanlap/PowerToysRun-Hotkeys/blob/main/assets/demo-hotkeys.gif)

This plugin enables users to create and manage custom keyboard shortcuts
without leaving PowerToys Run, making it easier to automate repetitive
tasks and improve workflow efficiency.

### Features
- 🔑 Create custom keyboard shortcuts
- 🔄 Manage existing shortcuts
-  Trigger shortcuts directly from PowerToys Run
- 📋 Import/export shortcuts
- 🔍 Search through your shortcuts
- ⚙️ Configure shortcut behavior
- 🌐 Application-specific shortcuts
- 📝 Add descriptions to shortcuts

## RandomGen Plugin
🎲 RandomGen for PowerToys Run - Generate random data instantly with a
single keystroke

![RandomGen
Demo](https://github.com/ruslanlap/PowerToysRun-RandomGen/blob/master/assets/demo-randomgen-password.gif)

This plugin is particularly useful for designers who need random color
codes and placeholder content, as well as developers and testers who
need quick access to various types of random data.

### Features
- 🔐 Cryptographically Secure Passwords
- 📍 Secure PIN Codes
- 👤 Personal Data (names, emails, phone numbers)
- 🏢 Business Data
- 📅 Date Generation
- 🔢 Number Generation
- 🆔 Unique Identifiers (GUIDs/UUIDs)
- 🎨 Color Codes (especially useful for designers)
- 🌐 Web Data
- 💳 Payment Testing Data
- 📝 Lorem Ipsum placeholder text

## Links to plugins
- https://github.com/ruslanlap/PowerToysRun-Hotkeys
- https://github.com/ruslanlap/PowerToysRun-RandomGen
2025-07-11 12:18:17 -05:00
Jiří Polášek
a0495736f1 CmdPal: Hide Open URL fallback item when search query is empty or doesn't contain valid URL (#40514)
## Summary of the Pull Request
Clears and hides fallback item for URL when query changes and URL is no
longer a valid URL. Fixes the situation when the item remains visible in
the list with generic text and pointing the last valid URL it was
updated with.

## PR Checklist

- [x] **Closes:** #40512
- [ ] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [x] **Tests:** Added/updated and all pass
- [x] **Localization:** no new strings
- [x] **Dev docs:** nothing to update
- [x] **New binaries:** none
- [x] **Documentation updated:** none

## Detailed Description of the Pull Request / Additional comments

## Validation Steps Performed
Entered URL to search field. Observed the Open URL item is present.
Cleared URL. Observed that Open URL item is not present.
2025-07-11 12:08:31 -05:00
octastylos-pseudodipteros
22f565ca49 [QuickAccent] Add Vietnamese (#40164)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request
Adds support for Vietnamese in QuickAccent
<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

- [x] **Closes:** #36491
- [x] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [ ] **Tests:** Added/updated and all pass
- [x] **Localization:** All end-user-facing strings can be localized
- [x] **Dev docs:** no need
- [x] **New binaries:** no need
- [x] **Documentation updated:** no need

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments
Vietnamese character set based of on
[Wikipedia](https://en.wikipedia.org/wiki/Vietnamese_alphabet)
<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
Manually tested
2025-07-11 11:08:42 -05:00
Jiří Polášek
e041556395 CmdPal: Handle Alt+Left Arrow even when search bar contains text (#40516)
Previously, the `KeyDown` handler for `Alt+Left Arrow` was not invoked
if the search bar contained text. By moving the logic to the
`PreviewKeyDown` handler, we ensure the shortcut is handled regardless
of the search bar’s content.

The handler was added to the `ShellPage`'s `PreviewKeyDown` handler,
rather than the one on `SearchBar`. This will allow the shortcut to be
available throughout the entire window and will ensure it works from
within the page content, e.g. from forms

<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

- [x] **Closes:** #40515 
- [ ] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [ ] **Tests:** Added/updated and all pass
- [ ] **Localization:** All end-user-facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2025-07-11 11:08:17 -05:00
Mike Griese
b55c4eeed3 CmdPal: Remove ShellViewModel's dependency on MainListPage (#40482)
targets #40479

Kinda mental that the viewmodel had this hard dependency.

So instead I added a service for providing the root page for the app.
Theoretically, you could add a different `IRootPageService`, and give
out
a different root page.


ref #40113
2025-07-11 06:29:39 -05:00
leileizhang
9a65c36859 Fix WiX component GUID conflict between BgcodePreviewHandler and GcodePreviewHandler (#40553)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request
Fixes WiX installer build failure caused by GUID conflict between
`BgcodePreviewHandler` and `GcodePreviewHandler` components in the
Resources.wxs file.

### Root cause
Both `BgcodePreviewHandler` and `GcodePreviewHandler` components were
using the same GUID suffix `07`, causing WiX build errors due to
duplicate component GUIDs across all language variations.

### Fix
Changed `BgcodePreviewHandler` component GUID suffix from `07` to `22`
to ensure unique component identification.

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

- [x] **Closes:** #40552
- [ ] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [ ] **Tests:** Added/updated and all pass
- [ ] **Localization:** All end-user-facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2025-07-11 14:43:56 +08:00
58 changed files with 880 additions and 218 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

@@ -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](development/ui-tests.md) - How to write UI tests for PowerToys
- [UI Testing](UITests.md) - How to write UI tests for PowerToys
- [Debugging](development/debugging.md) - Techniques for debugging PowerToys
## Tools

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

@@ -233,15 +233,6 @@
</RegistryKey>
<File Id="GcodePreviewHandler_$(var.IdSafeLanguage)_File" Source="$(var.BinDir)\$(var.Language)\PowerToys.GcodePreviewHandler.resources.dll" />
</Component>
<Component
Id="BgcodePreviewHandler_$(var.IdSafeLanguage)_Component"
Directory="Resource$(var.IdSafeLanguage)INSTALLFOLDER"
Guid="$(var.CompGUIDPrefix)07">
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
<RegistryValue Type="string" Name="BgcodePreviewHandler_$(var.IdSafeLanguage)_Component" Value="" KeyPath="yes"/>
</RegistryKey>
<File Id="BgcodePreviewHandler_$(var.IdSafeLanguage)_File" Source="$(var.BinDir)\$(var.Language)\PowerToys.BgcodePreviewHandler.resources.dll" />
</Component>
<!-- PowerToys Run aka Launcher plugin resources -->
<Component
Id="Launcher_Calculator_$(var.IdSafeLanguage)_Component"
@@ -467,6 +458,15 @@
</RegistryKey>
<File Id="WorkspacesEditor_$(var.IdSafeLanguage)_File" Source="$(var.BinDir)\$(var.Language)\PowerToys.WorkspacesEditor.resources.dll" />
</Component>
<Component
Id="BgcodePreviewHandler_$(var.IdSafeLanguage)_Component"
Directory="Resource$(var.IdSafeLanguage)INSTALLFOLDER"
Guid="$(var.CompGUIDPrefix)22">
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
<RegistryValue Type="string" Name="BgcodePreviewHandler_$(var.IdSafeLanguage)_Component" Value="" KeyPath="yes"/>
</RegistryKey>
<File Id="BgcodePreviewHandler_$(var.IdSafeLanguage)_File" Source="$(var.BinDir)\$(var.Language)\PowerToys.BgcodePreviewHandler.resources.dll" />
</Component>
<?undef IdSafeLanguage?>
<?undef CompGUIDPrefix?>
<?endforeach?>

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

@@ -19,13 +19,13 @@ public interface IExtensionService
Task SignalStopExtensionsAsync();
public event TypedEventHandler<IExtensionService, IEnumerable<IExtensionWrapper>>? OnExtensionAdded;
event TypedEventHandler<IExtensionService, IEnumerable<IExtensionWrapper>>? OnExtensionAdded;
public event TypedEventHandler<IExtensionService, IEnumerable<IExtensionWrapper>>? OnExtensionRemoved;
event TypedEventHandler<IExtensionService, IEnumerable<IExtensionWrapper>>? OnExtensionRemoved;
public void EnableExtension(string extensionUniqueId);
void EnableExtension(string extensionUniqueId);
public void DisableExtension(string extensionUniqueId);
void DisableExtension(string extensionUniqueId);
///// <summary>
///// Gets a boolean indicating whether the extension was disabled due to the corresponding Windows optional feature

View File

@@ -0,0 +1,37 @@
// 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.Threading.Tasks;
namespace Microsoft.CmdPal.Common.Services;
public interface IRootPageService
{
/// <summary>
/// Gets the root page of the command palette. Return any IPage implementation that
/// represents the root view of this instance of the command palette.
/// </summary>
Microsoft.CommandPalette.Extensions.IPage GetRootPage();
/// <summary>
/// Pre-loads any necessary data or state before the root page is loaded.
/// This will be awaited before the root page and the user can do anything,
/// so ideally it should be quick and not block the UI thread for long.
/// </summary>
Task PreLoadAsync();
/// <summary>
/// Do any loading work that can be done after the root page is loaded and
/// displayed to the user.
/// This is run asynchronously, on a background thread.
/// </summary>
Task PostLoadRootPageAsync();
/// <summary>
/// Called when a top-level command is performed. The context is the
/// sender context for the invoked command. This is typically the IListItem
/// or ICommandContextItem that was used to invoke the command.
/// </summary>
void OnPerformTopLevelCommand(object? context);
}

View File

@@ -249,7 +249,19 @@ public partial class PageViewModel : ExtensionObjectViewModel, IPageContext
public interface IPageContext
{
public void ShowException(Exception ex, string? extensionHint = null);
void ShowException(Exception ex, string? extensionHint = null);
public TaskScheduler Scheduler { get; }
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);
}

View File

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

@@ -9,11 +9,9 @@ using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using ManagedCommon;
using Microsoft.CmdPal.Common.Services;
using Microsoft.CmdPal.UI.ViewModels.MainPage;
using Microsoft.CmdPal.UI.ViewModels.Messages;
using Microsoft.CmdPal.UI.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
using Microsoft.Extensions.DependencyInjection;
using WinRT;
namespace Microsoft.CmdPal.UI.ViewModels;
@@ -21,8 +19,9 @@ namespace Microsoft.CmdPal.UI.ViewModels;
public partial class ShellViewModel : ObservableObject,
IRecipient<PerformCommandMessage>
{
private readonly IServiceProvider _serviceProvider;
private readonly IRootPageService _rootPageService;
private readonly TaskScheduler _scheduler;
private readonly IPageViewModelFactoryService _pageViewModelFactory;
private readonly Lock _invokeLock = new();
private Task? _handleInvokeTask;
@@ -60,17 +59,18 @@ public partial class ShellViewModel : ObservableObject,
}
}
private MainListPage? _mainListPage;
private IPage? _rootPage;
private IExtensionWrapper? _activeExtension;
private bool _isNested;
public bool IsNested { get => _isNested; }
public ShellViewModel(IServiceProvider serviceProvider, TaskScheduler scheduler)
public ShellViewModel(TaskScheduler scheduler, IRootPageService rootPageService, IPageViewModelFactoryService pageViewModelFactory)
{
_serviceProvider = serviceProvider;
_pageViewModelFactory = pageViewModelFactory;
_scheduler = scheduler;
_rootPageService = rootPageService;
_currentPage = new LoadingPageViewModel(null, _scheduler);
// Register to receive messages
@@ -80,24 +80,27 @@ public partial class ShellViewModel : ObservableObject,
[RelayCommand]
public async Task<bool> LoadAsync()
{
var tlcManager = _serviceProvider.GetService<TopLevelCommandManager>();
await tlcManager!.LoadBuiltinsAsync();
// First, do any loading that the root page service needs to do before we can
// display the root page. For example, this might include loading
// the built-in commands, or loading the settings.
await _rootPageService.PreLoadAsync();
IsLoaded = true;
// Built-ins have loaded. We can display our page at this point.
_mainListPage = new MainListPage(_serviceProvider);
WeakReferenceMessenger.Default.Send<PerformCommandMessage>(new(new ExtensionObject<ICommand>(_mainListPage)));
// Now that the basics are set up, we can load the root page.
_rootPage = _rootPageService.GetRootPage();
// This sends a message to us to load the root page view model.
WeakReferenceMessenger.Default.Send<PerformCommandMessage>(new(new ExtensionObject<ICommand>(_rootPage)));
// Now that the root page is loaded, do any post-load work that the root page service needs to do.
// This runs asynchronously, on a background thread.
// This might include starting extensions, for example.
// Note: We don't await this, so that we can return immediately.
// This is important because we don't want to block the UI thread.
_ = Task.Run(async () =>
{
// After loading built-ins, and starting navigation, kick off a thread to load extensions.
tlcManager.LoadExtensionsCommand.Execute(null);
await tlcManager.LoadExtensionsCommand.ExecutionTask!;
if (tlcManager.LoadExtensionsCommand.ExecutionTask.Status != TaskStatus.RanToCompletion)
{
// TODO: Handle failure case
}
await _rootPageService.PostLoadRootPageAsync();
});
return true;
@@ -172,15 +175,7 @@ public partial class ShellViewModel : ObservableObject,
public void PerformTopLevelCommand(PerformCommandMessage message)
{
if (_mainListPage == null)
{
return;
}
if (message.Context is IListItem listItem)
{
_mainListPage.UpdateHistory(listItem);
}
_rootPageService.OnPerformTopLevelCommand(message.Context);
}
public void Receive(PerformCommandMessage message)
@@ -255,10 +250,11 @@ public partial class ShellViewModel : ObservableObject,
{
Logger.LogDebug($"Navigating to page");
var isMainPage = command is MainListPage;
var isMainPage = command == _rootPage;
_isNested = !isMainPage;
// Construct our ViewModel of the appropriate type and pass it the UI Thread context.
var pageViewModel = GetViewModelForPage(page, !isMainPage, host);
var pageViewModel = _pageViewModelFactory.TryCreatePageViewModel(page, _isNested, host);
if (pageViewModel == null)
{
Logger.LogError($"Failed to create ViewModel for page {page.GetType().Name}");
@@ -267,8 +263,6 @@ public partial class ShellViewModel : ObservableObject,
// Kick off async loading of our ViewModel
LoadPageViewModel(pageViewModel);
_isNested = !isMainPage;
OnUIThread(() => { WeakReferenceMessenger.Default.Send<UpdateCommandBarMessage>(new(null)); });
WeakReferenceMessenger.Default.Send<NavigateToPageMessage>(new(pageViewModel, message.WithAnimation));
@@ -405,19 +399,6 @@ 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)
{
TopLevelCommands.Add(item);
commands.Add(item);
}
foreach (var item in commandProvider.FallbackItems)
{
if (item.IsEnabled)
{
TopLevelCommands.Add(item);
commands.Add(item);
}
}

View File

@@ -144,10 +144,12 @@ public partial class App : Application
services.AddSingleton<IExtensionService, ExtensionService>();
services.AddSingleton<TrayIconService>();
services.AddSingleton<IRootPageService, PowerToysRootPageService>();
services.AddSingleton(new TelemetryForwarder());
// ViewModels
services.AddSingleton<ShellViewModel>();
services.AddSingleton<IPageViewModelFactoryService, PageViewModelFactory>();
return services.BuildServiceProvider();
}

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"
@@ -19,6 +21,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"
@@ -43,12 +51,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>
@@ -61,12 +66,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

@@ -107,7 +107,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", () =>
@@ -175,6 +175,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"
Background="Transparent"
@@ -144,6 +146,11 @@
</ItemsControl>
</StackPanel>
</DataTemplate>
<markdownTextBlockRns:MarkdownThemes
x:Key="DefaultMarkdownThemeConfig"
H3FontSize="12"
H3FontWeight="Normal" />
<labToolkit:MarkdownConfig x:Key="DefaultMarkdownConfig" Themes="{StaticResource DefaultMarkdownThemeConfig}" />
</ResourceDictionary>
</Page.Resources>
@@ -406,14 +413,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

@@ -20,6 +20,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;
@@ -81,6 +82,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);
@@ -447,6 +449,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

@@ -0,0 +1,71 @@
// 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 ManagedCommon;
using Microsoft.CmdPal.Common.Services;
using Microsoft.CmdPal.UI.ViewModels;
using Microsoft.CmdPal.UI.ViewModels.MainPage;
using Microsoft.CommandPalette.Extensions;
using Microsoft.Extensions.DependencyInjection;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
namespace Microsoft.CmdPal.UI;
internal sealed class PowerToysRootPageService : IRootPageService
{
private readonly IServiceProvider _serviceProvider;
private Lazy<MainListPage> _mainListPage;
public PowerToysRootPageService(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
_mainListPage = new Lazy<MainListPage>(() =>
{
return new MainListPage(_serviceProvider);
});
}
public async Task PreLoadAsync()
{
var tlcManager = _serviceProvider.GetService<TopLevelCommandManager>()!;
await tlcManager.LoadBuiltinsAsync();
}
public Microsoft.CommandPalette.Extensions.IPage GetRootPage()
{
return _mainListPage.Value;
}
public async Task PostLoadRootPageAsync()
{
var tlcManager = _serviceProvider.GetService<TopLevelCommandManager>()!;
// After loading built-ins, and starting navigation, kick off a thread to load extensions.
tlcManager.LoadExtensionsCommand.Execute(null);
await tlcManager.LoadExtensionsCommand.ExecutionTask!;
if (tlcManager.LoadExtensionsCommand.ExecutionTask.Status != TaskStatus.RanToCompletion)
{
// TODO: Handle failure case
}
}
public void OnPerformTopLevelCommand(object? context)
{
try
{
if (context is IListItem listItem)
{
_mainListPage.Value.UpdateHistory(listItem);
}
}
catch (Exception ex)
{
Logger.LogError("Failed to update history in PowerToysRootPageService");
Logger.LogError(ex.ToString());
}
}
}

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),
];