Compare commits

...

33 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
leileizhang
668f3f3ebd [pipeline] Reduce CI, Fuzz, and UI Test Timeout from 4 Hours to 90 Minutes (#40500)
<!-- 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

The previous timeout for CI, fuzzing, and UI test builds was set to 4
hours, which often caused long wait times even when a job was already
stuck or failing.

To improve development feedback loops and reduce wasted time, the
timeout has been shortened to 90 minutes, which should still be
sufficient for normal runs but helps catch issues earlier.

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

- [ ] **Closes:** #xxx
- [ ] **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:23:58 +08:00
Jiří Polášek
28a4014673 CmdPal: Ensure that DismissMessage handler calls HideWindow on UI thread (#40536)
## Summary of the Pull Request
Otherwise, it silently fails to hide the window.
The problem is not visible to naked eye, since the window is already
cloaked.

## PR Checklist

- [x] **Closes:** #40535 
- [ ] **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
- [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

## Detailed Description of the Pull Request / Additional comments

## Validation Steps Performed
Tested with an extension that return CommandResult.Dismiss and then
works with focused app
(https://github.com/CoreyHayward/CmdPal-InputTyper).
2025-07-10 21:42:14 -05:00
Jiří Polášek
beb85e69a8 CmdPal: Allow mouse X1 button to navigate back (#40517)
## Summary of the Pull Request
Enables navigation to the previous page when the mouse X1 (back) button
is pressed. This improves user experience for those using multi-button
mice, making navigation more intuitive.

## PR Checklist

- [x] **Closes:** #40499
- [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
- [x] **Dev docs:** nope
- [x] **New binaries:** nada
- [x] **Documentation updated:** nothing to add

## Detailed Description of the Pull Request / Additional comments

## Validation Steps Performed
2025-07-10 14:56:00 -05:00
MaoShengelia
dbad946b6d Adding Lock to RecentCommandsManager (#40507)
According to Issue #40447 Without the Lock in RecentCommandsManager we
get Exception:
Collection was modified; enumeration operation may not execute.

It indicated that while GetCommandHistoryWeight was enumerating History,
another method (likely AddHistoryItem) modified it at the same time.
Since List is not thread-safe, simultaneous read+write can break it.

## Summary of the Pull Request

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

- [ ] **Closes:** #xxx
- [ ] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [ ] **Tests:** Unit Tests all pass, Manually Tested as Well
- [ ] **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

I've checked that in this project we use the new .NET 9 locking instead
of object locking and implemented it according to rest of the locks
## Detailed Description of the Pull Request / Additional comments

I've Manually tested it and also made sure that all the unit test pass
## Validation Steps Performed
2025-07-10 13:10:38 -05:00
Jiří Polášek
74b6650a19 Set Command Palette window as the topmost when shown and move to the bottom when hidden (#40444)
## Summary of the Pull Request

Ensures the Command Palette main window is brought to the front and made
topmost when shown. When the palette is hidden (cloaked but not
destroyed), it is moved to the bottom of the window stack to avoid
interfering with the user workflow. Dynamic removal of the topmost flag
is a precaution to prevent the cloak window from bothering user in
accessibility tools that too much -- in case they don't recognize cloak
windows as not visible).

## PR Checklist

- [x] **Closes:** #38726 
- [ ] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [x] **Tests:** Nothing to test
- [x] **Localization:** Nothing to localize
- [x] **Dev docs:** Nothing to update
- [x] **New binaries:** No new binaries
- [x] **Documentation updated:** nothing to update

## Detailed Description of the Pull Request / Additional comments

## Validation Steps Performed

I've tested that the Command Palette window is display over other
top-most windows (e.g. Task Manager). I've tested that the command
palette is displayed over the Rainbow gadgets when it was originally
under the gadget.
2025-07-10 06:09:04 -05:00
David Federman
5fd8374c40 Re-enable MSBuildCache (#40261)
Re-enable MSBuildCache
2025-07-10 06:08:28 -05:00
PesBandi
47833b8785 [CmdPal][Calc]Also handle normal spaces when the group separator is a non-breaking one (#40328)
## Summary of the Pull Request
Fixes space handling for CmdPal's Calculator. Windows uses the no-break
space instead of the normal one for locales which use a space for number
group separation, however most users don't realize this and expect
CmdPal to also handle normal spaces as such, hence this PR.
## PR Checklist

- [x] **Closes:** #40273
- [x] **Communication:** I've discussed this with core contributors
already.
- [ ] **Tests:** Added/updated and all pass
- [x] **Localization:** All end-user-facing strings can be localized
- [x] **Dev docs:** No need
- [x] **New binaries:** None
- [x] **Documentation updated:** No need

## Detailed Description of the Pull Request / Additional comments

![image](https://github.com/user-attachments/assets/34e261c1-1d16-42d6-8f82-22aa55a43d7e)
## Validation Steps Performed
Manually tested calculations with spaces as group separators. Doesn't
break with lone standing spaces (e.g. `7 + pi + pi + 7`).
2025-07-10 06:07:25 -05:00
Pedro Lamas
071f5d7bcc Adds BgcodeThumbnailProvider and BgcodePreviewHandler (#38667)
<!-- 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:** #30352
- [X] **Communication:** I've discussed this with core contributors
already. If 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
- [X] **New binaries:** Added on the required places
- [X] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [X] [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


![image](https://github.com/user-attachments/assets/62c0cbbb-fbca-4bb3-82fe-696ba40da83d)


![image](https://github.com/user-attachments/assets/3f2f1346-91fb-4f49-85b9-8cd6e19e68e9)

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

- Close PowerToys if installed in your machine
- Full build the solution: PowerToys.sln
- Start PowerToys from PowerToys\x64\Debug\PowerToys.exe or
PowerToys\x64\Release\PowerToys.exe
- Toggle the "Binary G-code thumbnail previewer" setting to enable
- Open HelperFiles folder on the tests and check if the icon changes to
an image
- Check explorer preview to see if image is also shown there

---------

Co-authored-by: leileizhang <leilzh@microsoft.com>
2025-07-10 17:20:30 +08:00
TheBestWebsite
1feb7d5e5c Update README links and labels for 0.92.1 (#40476)
<!-- 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
Simply updates the install links in the README.
<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

- **Closes:** N/A
- **Communication:** This is a very minor change, for which
communication would only waste time, even if the work is rejected.
Communication is therefore unnecessary.
- **Tests:** None needed.
- [X] **Localization:** All end-user-facing strings can be localized
- **Dev docs:** No updates needed.
- **New binaries:** None needed
- [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries: not part of program
- [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder: Not part of program
- [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects: No tests involved.
- [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml):
N/A
- **Documentation updated:** No update needed
<!-- 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
Figuring a way to automate this would be useful, as the updates for
patch releases are often overlooked.
<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
Preview viewed and links tested.
2025-07-10 13:43:07 +08:00
Mike Griese
cfa5f75862 CmdPal: Add settings to control which fallbacks are enabled (#40505)
This adds settings to each provider to allow us to control if individual
fallback items are enabled or not, regardless of the provider being
enabled.

This is relevant to _all the threads where disabling fallback commands
came up_

This just adds another section to each provider's settings page, with a
list of the fallback commands.

This also has nothing to do with the "top-level apps search", which is
not really a fallback command - it's its own thing.

Ref #38288. Doesn't close that, because this only controls
enable/disable, not ranking.

From here, we should be able to add a dedicated page in the SUI that
shows all the fallbacks across all providers. That's where we'll enable
the ordering.
2025-07-09 22:01:38 -05:00
Jiří Polášek
5c6166bc9f Fix CmdPal application activation (#40162)
<!-- 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 PR:
- Fixes the passing of activation arguments between instances.
- Adds defensive handling of exceptions if the activation argument is
corrupted.
- Ensures that the activation method is consistently handled on the main
thread.
- Updates the condition under which the main window is displayed after
startup or activation.

The main window will now be automatically shown when the app is started
or activated, except in the following cases:
- when started as a startup task,
- when started using x-cmdpal://background,
- when started using x-cmdpal://settings.

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

- [x] **Closes:** #39681
- [ ] **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

---------

Co-authored-by: Mike Griese <migrie@microsoft.com>
2025-07-09 21:49:14 -05:00
Copilot
7a1c27dcf3 Add hotkey shortcuts to Command Palette context menu items (#40359)
## Summary

Adds keyboard shortcuts to Command Palette context menu items to match
PowerToys Run functionality, providing users with faster access to
context actions without needing to open the context menu.

## Changes Made

Added `RequestedShortcut` properties to context menu items in both
`UWPApplication.cs` and `Win32Program.cs`:

### Keyboard Shortcuts Implemented

**UWP Applications:**
- Run as Admin: `Ctrl+Shift+Enter`
- Copy Path: `Ctrl+Shift+P`
- Open Containing Folder: `Ctrl+Shift+E`
- Open in Console: `Ctrl+Shift+C`

**Win32 Programs:**
- Run as Admin: `Ctrl+Shift+Enter`
- Run as Different User: `Ctrl+Shift+U`
- Copy Path: `Ctrl+Shift+P`
- Open Containing Folder: `Ctrl+Shift+E`
- Open in Console: `Ctrl+Shift+C`

## Implementation Details

- Added `using Windows.System;` import to access `VirtualKey` enum
- Used `KeyChordHelpers.FromModifiers()` to create keyboard shortcuts
- Applied shortcuts to `CommandContextItem` objects in `GetCommands()`
methods
- Maintained all existing functionality while adding hotkey
accessibility

### Code Example

```csharp
commands.Add(new CommandContextItem(
    new RunAsAdminCommand(path, directory, false))
{
    RequestedShortcut = KeyChordHelpers.FromModifiers(ctrl: true, shift: true, vkey: VirtualKey.Enter),
});
```

## User Experience

Users can now:
- Select an app in Command Palette search results
- Press hotkeys directly (e.g., `Ctrl+Shift+E` to open containing
folder)
- Access context actions without opening the context menu (`Ctrl+K`)
- Enjoy the same hotkey experience as PowerToys Run

This makes Command Palette faster and more consistent with PowerToys
Run, addressing the user request for "having a possibility to directly
trigger any of those options with hotkey from the search results."

Fixes #40358.

<!-- START COPILOT CODING AGENT TIPS -->
---

💬 Share your feedback on Copilot coding agent for the chance to win a
$200 gift card! Click
[here](https://survey.alchemer.com/s3/8343779/Copilot-Coding-agent) to
start the survey.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: zadjii-msft <18356694+zadjii-msft@users.noreply.github.com>
2025-07-09 21:45:27 -05:00
Mike Griese
d944b8728c cmdpal: A sample for tracking load/unload for pages (#39265)
Committing this sample for posterity's sake.

This is the product of an experiment: "can an extension know when it's
page was opened / closed?"

And without changing the actual SDK, this **is** possible. It relies on
the fact that CmdPal (the host) needs to register for the `ItemsChanged`
event on list pages, and it does that when the page is loaded, and it
unregisters itself when the page is closed.

This does require manually implementing `IListPage` - you can't just
`override` the `event` in the base class ([that's
illegal](https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1070)),
and using `new` to shadow it doesn't work either (probably for cswinrt
reasons).

This is the best I came up with.
2025-07-09 21:05:20 -05:00
Jiří Polášek
3c6fa44bf2 Prevent apps from appearing in top-level search when Installed apps extension is disabled (#40132)
<!-- 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
Prevents installed applications from appearing in the top-level search
when Installed Apps extension is disabled.

Previously, application commands were still returned in the global
search results even when the *Installed Apps* extension was turned off.
To match user expectations, the search now respects the extension’s
enabled state.


- Added `IsActive` property to `CommandProviderWrapper` to indicate
whether the provider is both valid and enabled by the user in the
settings.
- Updated `MainListPage` to verify that the provider for `AllApps` is
active before including apps in filtered results.

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

- [x] **Closes:** #39937
- [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
- [x] **Dev docs:** nothing to update
- [x] **New binaries:** none
- [x] **Documentation updated:** nothing to update

<!-- 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
Verified that the Installed app entries are shown in the top-level
search only when the Installed apps extension is enabled. Verified that
turning the Installed apps extension on or off has an immediate effect,
and that the behavior persists after an application restart.
2025-07-09 20:44:08 -05:00
leileizhang
6d29c3a2c9 [pipeline] feat: Implement flexible UI test pipeline with configurable build and execution modes (#40490)
<!-- 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
**Root Cause:**
The current pipeline builds the entire solution and runs all UI tests
every time, which takes more than 2 hours to complete.

**Fix**
Make the PowerToys UI test pipeline provides flexible options for
building and testing:

### Pipeline Options

- **useLatestOfficialBuild**: When checked, downloads the latest
official PowerToys build and installs it for testing. This skips the
full solution build and only builds UI test projects.

- **useCurrentBranchBuild**: When checked along with
`useLatestOfficialBuild`, downloads the official build from the current
branch instead of main.

- **uiTestModules**: Specify which UI test modules to build and run.
Examples:
  - `UITests-FancyZones` - Only FancyZones UI tests
  - `MouseUtils.UITests` - Only MouseUtils UI tests
- `['UITests-FancyZones', 'MouseUtils.UITests']` - Multiple specific
modules
  - Leave empty to build and run all UI test modules

### Build Modes

1. **Official Build + Selective Testing** (`useLatestOfficialBuild =
true`)
   - Downloads and installs official PowerToys build
   - Builds only specified UI test projects
   - Runs specified UI tests against installed PowerToys
   - Controlled by `uiTestModules` parameter

2. **Full Build + Testing** (`useLatestOfficialBuild = false`)
   - Builds entire PowerToys solution
   - Builds UI test projects (all or specific based on `uiTestModules`)
   - Runs UI tests (all or specific based on `uiTestModules`)
   - Uses freshly built PowerToys for testing
  
<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

- [ ] **Closes:** #xxx
- [ ] **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
- [ ] **Localization:** All end-user-facing strings can be localized
- [x] **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-10 09:26:26 +08:00
Mike Griese
608eb1e034 CmdPal: Pull out VM bits from ShellPage.xaml.cs (#40479)
ref #40113

Moves a lot of the "model" logic out of `ShellPage.xaml.cs` into
`ShellViewModel`.

The LARGE majority of this code is copy-paste moving code. We're now
using a couple more messages to pass navigation between the VM and the
page. And a couple new messages for passing ETW events.
2025-07-09 18:49:21 -05:00
Niels Laute
f341aeb627 [UX] Improved formatting for release notes (#40320)
## Summary of the Pull Request

Before vs after:
![Screenshot 2025-07-01
130138](https://github.com/user-attachments/assets/f850298f-fe0b-43c1-9bae-eee712dec5e8)

## PR Checklist

- [x] **Closes:** #40319
- [ ] **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-09 16:19:15 -05:00
Mike Griese
ee764d5f56 cmdpal: Re-re-enable the clipboard history (#40471)
_⚠️ targets #40445_

This time, for real

This really really re-enables the clipboard history command. With the
foreground fixes from #40445, we can properly dismiss ourself to give FG
to the next app window. This actually lets us paste correctly.


I took the liberty of localizing the strings and fixing up the icons
while I was at it.

Closes #38344
2025-07-09 15:42:46 -05:00
200 changed files with 6229 additions and 710 deletions

View File

@@ -29,11 +29,14 @@ RUS
AYUV
bak
Bcl
bgcode
Deflatealgorithm
exa
exabyte
Gbits
Gbps
gcode
Heatshrink
Mbits
MBs
mkv

View File

@@ -18,6 +18,7 @@
/TestFiles/
[^/]\.cur$
[^/]\.gcode$
[^/]\.bgcode$
[^/]\.rgs$
\.a$
\.ai$

View File

@@ -76,6 +76,7 @@ ARPINSTALLLOCATION
ARPPRODUCTICON
ARRAYSIZE
ARROWKEYS
ARTIFACTSTAGINGDIRECTORY
asf
Ashcraft
AShortcut
@@ -286,6 +287,7 @@ CVal
cvd
CVirtual
CVS
CWMO
CXSCREEN
CXSMICON
CXVIRTUALSCREEN
@@ -1381,6 +1383,7 @@ RIGHTSCROLLBAR
riid
RKey
RNumber
Rns
rop
ROUNDSMALL
ROWSETEXT
@@ -1722,6 +1725,8 @@ UFlags
UHash
UIA
UIEx
uild
uitests
ULONGLONG
ums
uncompilable
@@ -1748,6 +1753,7 @@ Uptool
urld
Usb
USEDEFAULT
USEINSTALLERFORTEST
USEFILEATTRIBUTES
USESHOWWINDOW
USESTDHANDLES
@@ -1927,6 +1933,7 @@ Wubi
WUX
Wwanpp
XAxis
XButton
xclip
xcopy
XDeployment

View File

@@ -66,6 +66,12 @@
"PowerToys.GcodeThumbnailProvider.dll",
"PowerToys.GcodeThumbnailProvider.exe",
"PowerToys.GcodeThumbnailProviderCpp.dll",
"PowerToys.BgcodePreviewHandler.dll",
"PowerToys.BgcodePreviewHandler.exe",
"PowerToys.BgcodePreviewHandlerCpp.dll",
"PowerToys.BgcodeThumbnailProvider.dll",
"PowerToys.BgcodeThumbnailProvider.exe",
"PowerToys.BgcodeThumbnailProviderCpp.dll",
"PowerToys.ManagedTelemetry.dll",
"PowerToys.MarkdownPreviewHandler.dll",
"PowerToys.MarkdownPreviewHandler.exe",

View File

@@ -0,0 +1,46 @@
param(
[Parameter()]
[ValidateSet("Machine", "PerUser")]
[string]$InstallMode = "Machine"
)
$ProgressPreference = 'SilentlyContinue'
# Get artifact path
$ArtifactPath = $ENV:BUILD_ARTIFACTSTAGINGDIRECTORY
if (-not $ArtifactPath) {
throw "BUILD_ARTIFACTSTAGINGDIRECTORY environment variable not set"
}
# Since we only download PowerToysSetup-*.exe files, we can directly find it
$Installer = Get-ChildItem -Path $ArtifactPath -Filter 'PowerToys*.exe' | Select-Object -First 1
if (-not $Installer) {
throw "PowerToys installer not found"
}
Write-Host "Installing PowerToys: $($Installer.Name)"
# Install PowerToys
$Process = Start-Process -Wait -FilePath $Installer.FullName -ArgumentList "/passive", "/norestart" -PassThru -NoNewWindow
if ($Process.ExitCode -eq 0 -or $Process.ExitCode -eq 3010) {
Write-Host "✅ PowerToys installation completed successfully"
} else {
throw "PowerToys installation failed with exit code: $($Process.ExitCode)"
}
# Verify installation
if ($InstallMode -eq "PerUser") {
if (Test-Path "${env:LOCALAPPDATA}\PowerToys\PowerToys.exe") {
Write-Host "✅ PowerToys verified at: ${env:LOCALAPPDATA}\PowerToys\PowerToys.exe"
} else {
throw "PowerToys installation verification failed"
}
} else {
if (Test-Path "${env:ProgramFiles}\PowerToys\PowerToys.exe") {
Write-Host "✅ PowerToys verified at: ${env:ProgramFiles}\PowerToys\PowerToys.exe"
} else {
throw "PowerToys installation verification failed"
}
}

View File

@@ -19,7 +19,7 @@ parameters:
- name: enableMsBuildCaching
type: boolean
displayName: "Enable MSBuild Caching"
default: false
default: true
- name: runTests
type: boolean
displayName: "Run Tests"
@@ -36,7 +36,8 @@ extends:
template: templates/pipeline-ci-build.yml
parameters:
buildPlatforms: ${{ parameters.buildPlatforms }}
enableMsBuildCaching: ${{ parameters.enableMsBuildCaching }}
${{ if eq(variables['System.PullRequest.IsFork'], 'False') }}:
enableMsBuildCaching: ${{ parameters.enableMsBuildCaching }}
runTests: ${{ parameters.runTests }}
useVSPreview: ${{ parameters.useVSPreview }}
useLatestWebView2: ${{ parameters.useLatestWebView2 }}

View File

@@ -19,7 +19,7 @@ parameters:
- name: enableMsBuildCaching
type: boolean
displayName: "Enable MSBuild Caching"
default: false
default: true
- name: runTests
type: boolean
displayName: "Run Tests"
@@ -42,7 +42,8 @@ extends:
template: templates/pipeline-ci-build.yml
parameters:
buildPlatforms: ${{ parameters.buildPlatforms }}
enableMsBuildCaching: ${{ parameters.enableMsBuildCaching }}
${{ if eq(variables['System.PullRequest.IsFork'], 'False') }}:
enableMsBuildCaching: ${{ parameters.enableMsBuildCaching }}
runTests: ${{ parameters.runTests }}
useVSPreview: ${{ parameters.useVSPreview }}
useLatestWinAppSDK: ${{ parameters.useLatestWinAppSDK }}

View File

@@ -32,7 +32,7 @@ parameters:
- name: enableMsBuildCaching
type: boolean
displayName: "Enable MSBuild Caching"
default: false
default: true
- name: runTests
type: boolean
displayName: "Run Tests"
@@ -46,6 +46,7 @@ extends:
template: templates/pipeline-ci-build.yml
parameters:
buildPlatforms: ${{ parameters.buildPlatforms }}
enableMsBuildCaching: ${{ parameters.enableMsBuildCaching }}
${{ if eq(variables['System.PullRequest.IsFork'], 'False') }}:
enableMsBuildCaching: ${{ parameters.enableMsBuildCaching }}
runTests: ${{ parameters.runTests }}
useVSPreview: ${{ parameters.useVSPreview }}

View File

@@ -43,6 +43,7 @@ stages:
enableMsBuildCaching: ${{ parameters.enableMsBuildCaching }}
runTests: true
useVSPreview: ${{ parameters.useVSPreview }}
timeoutInMinutes: 90
- stage: OneFuzz
displayName: Fuzz ${{ parameters.platform }}

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

@@ -81,6 +81,12 @@ parameters:
- 'src/modules/previewpane/SvgPreviewHandler/SvgPreviewHandler.csproj'
- 'src/modules/previewpane/SvgThumbnailProvider/SvgThumbnailProvider.csproj'
- 'src/modules/FileLocksmith/FileLocksmithUI/FileLocksmithUI.csproj'
- name: timeoutInMinutes
type: number
default: 240
- name: cancelTimeoutInMinutes
type: number
default: 1
jobs:
- job: ${{ parameters.jobName }}
@@ -123,8 +129,8 @@ jobs:
${{ else }}:
RestoreAdditionalProjectSourcesArg: ''
displayName: Build
timeoutInMinutes: 240
cancelTimeoutInMinutes: 1
timeoutInMinutes: ${{ parameters.timeoutInMinutes }}
cancelTimeoutInMinutes: ${{ parameters.cancelTimeoutInMinutes }}
templateContext: # Required when this template is hosted in 1ES PT
outputs:
- output: pipelineArtifact

View File

@@ -0,0 +1,132 @@
# Minimal UI Tests Build Template
# This template only builds UI test projects and stages their test DLLs for consumption by test pipelines
parameters:
- name: buildConfigurations
type: object
default:
- Release
- name: buildPlatforms
type: object
default:
- x64
- name: condition
type: string
default: ''
- name: dependsOn
type: object
default: []
- name: pool
type: object
default: []
- name: variables
type: object
default: {}
- name: uiTestModules
type: object
default: []
jobs:
- job: BuildUITests
${{ if ne(length(parameters.pool), 0) }}:
pool: ${{ parameters.pool }}
dependsOn: ${{ parameters.dependsOn }}
condition: ${{ parameters.condition }}
strategy:
matrix:
${{ each config in parameters.buildConfigurations }}:
${{ each platform in parameters.buildPlatforms }}:
${{ config }}_${{ platform }}:
BuildConfiguration: ${{ config }}
BuildPlatform: ${{ platform }}
variables:
JobOutputDirectory: $(Build.ArtifactStagingDirectory)
LogOutputDirectory: $(Build.ArtifactStagingDirectory)\logs
JobOutputArtifactName: build-$(BuildPlatform)-$(BuildConfiguration)
NUGET_RESTORE_MSBUILD_ARGS: /p:Platform=$(BuildPlatform)
${{ insert }}: ${{ parameters.variables }}
displayName: Build UI Tests Only
timeoutInMinutes: 60
cancelTimeoutInMinutes: 1
templateContext:
outputs:
- output: pipelineArtifact
artifactName: $(JobOutputArtifactName)
targetPath: $(Build.ArtifactStagingDirectory)
steps:
- checkout: self
clean: true
submodules: true
persistCredentials: True
fetchTags: false
fetchDepth: 1
- template: steps-ensure-dotnet-version.yml
parameters:
sdk: true
version: '9.0'
- template: .\steps-restore-nuget.yml
- task: NuGetCommand@2
displayName: Restore solution-level NuGet packages
inputs:
command: restore
feedsToUse: config
configPath: nuget.config
restoreSolution: PowerToys.sln
restoreDirectory: '$(Build.SourcesDirectory)\packages'
# Build all UI test projects if no specific modules are specified
- ${{ if eq(length(parameters.uiTestModules), 0) }}:
- task: VSBuild@1
displayName: Build UI Test Projects
inputs:
solution: '**/*UITest*.csproj'
vsVersion: 17.0
msbuildArgs: >-
-restore
-graph
/p:RestorePackagesConfig=true
/p:BuildProjectReferences=true
/p:CIBuild=true
/bl:$(LogOutputDirectory)\build-all-uitests.binlog
$(NUGET_RESTORE_MSBUILD_ARGS)
platform: $(BuildPlatform)
configuration: $(BuildConfiguration)
msbuildArchitecture: x64
maximumCpuCount: true
# Build specific UI test modules
- ${{ if ne(length(parameters.uiTestModules), 0) }}:
- ${{ each module in parameters.uiTestModules }}:
- task: VSBuild@1
displayName: 'Build UI Test Module: ${{ module }}'
inputs:
solution: '**/*${{ module }}*.csproj'
vsVersion: 17.0
msbuildArgs: >-
-restore
-graph
/p:RestorePackagesConfig=true
/p:BuildProjectReferences=true
/p:CIBuild=true
/bl:$(LogOutputDirectory)\build-${{ module }}.binlog
$(NUGET_RESTORE_MSBUILD_ARGS)
platform: $(BuildPlatform)
configuration: $(BuildConfiguration)
msbuildArchitecture: x64
maximumCpuCount: true
# Stage test project outputs with directory structure
- task: CopyFiles@2
displayName: Stage UI Test Build Outputs
inputs:
sourceFolder: '$(Build.SourcesDirectory)'
contents: '$(BuildPlatform)/$(BuildConfiguration)/**/*'
targetFolder: '$(JobOutputDirectory)\$(BuildPlatform)\$(BuildConfiguration)'
- publish: $(JobOutputDirectory)
artifact: $(JobOutputArtifactName)
displayName: Publish UI Test artifacts
condition: always()

View File

@@ -11,10 +11,28 @@ parameters:
- name: useLatestWebView2
type: boolean
default: false
- name: useLatestOfficialBuild
type: boolean
default: true
- name: useCurrentBranchBuild
type: boolean
default: false
- name: uiTestModules
type: object
default: []
- name: installMode
type: string
default: 'machine'
values:
- 'machine'
- 'peruser'
- name: jobSuffix
type: string
default: ''
jobs:
- job: Test${{ parameters.platform }}${{ parameters.configuration }}
displayName: Test ${{ parameters.platform }} ${{ parameters.configuration }}
- job: Test${{ parameters.platform }}${{ parameters.configuration }}${{ parameters.jobSuffix }}
displayName: Test ${{ parameters.platform }} ${{ parameters.configuration }}${{ parameters.jobSuffix }}
timeoutInMinutes: 300
variables:
${{ if or(eq(parameters.platform, 'x64Win10'), eq(parameters.platform, 'x64Win11')) }}:
@@ -95,28 +113,80 @@ jobs:
& '$(build.sourcesdirectory)\.pipelines\InstallWinAppDriver.ps1'
displayName: Download and install WinAppDriver
- ${{ if eq(parameters.useLatestOfficialBuild, true) }}:
- task: DownloadPipelineArtifact@2
inputs:
buildType: 'specific'
project: 'Dart'
definition: '76541'
buildVersionToDownload: 'latestFromBranch'
${{ if eq(parameters.useCurrentBranchBuild, true) }}:
branchName: '$(Build.SourceBranch)'
${{ else }}:
branchName: 'refs/heads/main'
artifactName: 'build-$(BuildPlatform)-Release'
targetPath: '$(Build.ArtifactStagingDirectory)'
${{ if eq(parameters.installMode, 'peruser') }}:
patterns: |
**/PowerToysUserSetup*.exe
${{ else }}:
patterns: |
**/PowerToysSetup*.exe
- ${{ if eq(parameters.useLatestOfficialBuild, true) }}:
- ${{ if eq(parameters.installMode, 'peruser') }}:
- pwsh: |-
& "$(build.sourcesdirectory)\.pipelines\installPowerToys.ps1" -InstallMode "PerUser"
displayName: Install PowerToys (Per-User)
- ${{ if eq(parameters.installMode, 'machine') }}:
- pwsh: |-
& "$(build.sourcesdirectory)\.pipelines\installPowerToys.ps1" -InstallMode "Machine"
displayName: Install PowerToys (Machine-Level)
- ${{ if ne(parameters.platform, 'arm64') }}:
- task: ScreenResolutionUtility@1
inputs:
displaySettings: 'optimal'
- task: VSTest@3
displayName: Run UI Tests
inputs:
platform: '$(BuildPlatform)'
configuration: '$(BuildConfiguration)'
testSelector: 'testAssemblies'
searchFolder: '$(Pipeline.Workspace)\$(TestArtifactsName)'
vsTestVersion: 'toolsInstaller'
uiTests: true
rerunFailedTests: true
# Since UITests-FancyZonesEditor.dll is generated in both UITests-FancyZonesEditor and UITests-FancyZones, removed one to avoid duplicate test runs
testAssemblyVer2: |
**\*UITest*.dll
!**\obj\**
!**\ref\**
!**\UITests-FancyZones\**\UITests-FancyZonesEditor.dll
- ${{ if eq(length(parameters.uiTestModules), 0) }}:
- task: VSTest@3
displayName: Run UI Tests
inputs:
platform: '$(BuildPlatform)'
configuration: '$(BuildConfiguration)'
testSelector: 'testAssemblies'
searchFolder: '$(Pipeline.Workspace)\$(TestArtifactsName)'
vsTestVersion: 'toolsInstaller'
uiTests: true
rerunFailedTests: true
# Since UITests-FancyZonesEditor.dll is generated in both UITests-FancyZonesEditor and UITests-FancyZones, removed one to avoid duplicate test runs
testAssemblyVer2: |
**\*UITest*.dll
!**\obj\**
!**\ref\**
!**\UITests-FancyZones\**\UITests-FancyZonesEditor.dll
env:
platform: '$(TestPlatform)'
useInstallerForTest: ${{ parameters.useLatestOfficialBuild }}
env:
platform: '$(TestPlatform)'
- ${{ if ne(length(parameters.uiTestModules), 0) }}:
- ${{ each module in parameters.uiTestModules }}:
- task: VSTest@3
displayName: Run UI Test - ${{ module }}
inputs:
platform: '$(BuildPlatform)'
configuration: '$(BuildConfiguration)'
testSelector: 'testAssemblies'
searchFolder: '$(Pipeline.Workspace)\$(TestArtifactsName)'
vsTestVersion: 'toolsInstaller'
uiTests: true
rerunFailedTests: true
testAssemblyVer2: |
**\*${{ module }}*.dll
!**\obj\**
!**\ref\**
!**\UITests-FancyZones\**\UITests-FancyZonesEditor.dll
env:
platform: '$(TestPlatform)'
useInstallerForTest: ${{ parameters.useLatestOfficialBuild }}

View File

@@ -3,9 +3,6 @@ variables:
value: false
- name: EnablePipelineCache
value: true
- ${{ if eq(parameters.enableMsBuildCaching, true) }}:
- name: EnablePipelineCache
value: true
parameters:
- name: buildPlatforms
@@ -60,4 +57,5 @@ stages:
useLatestWinAppSDK: ${{ parameters.useLatestWinAppSDK }}
${{ if eq(parameters.useLatestWinAppSDK, true) }}:
winAppSDKVersionNumber: ${{ parameters.winAppSDKVersionNumber }}
useExperimentalVersion: ${{ parameters.useExperimentalVersion }}
useExperimentalVersion: ${{ parameters.useExperimentalVersion }}
timeoutInMinutes: 90

View File

@@ -3,9 +3,6 @@ variables:
value: false
- name: EnablePipelineCache
value: true
- ${{ if eq(parameters.enableMsBuildCaching, true) }}:
- name: EnablePipelineCache
value: true
parameters:
- name: buildPlatforms
@@ -22,63 +19,155 @@ parameters:
- name: useLatestWebView2
type: boolean
default: false
- name: useLatestOfficialBuild
type: boolean
default: true
- name: testBothInstallModes
type: boolean
default: true
- name: useCurrentBranchBuild
type: boolean
default: false
- name: uiTestModules
type: object
default: []
stages:
- ${{ each platform in parameters.buildPlatforms }}:
- stage: Build_${{ platform }}
displayName: Build ${{ platform }}
dependsOn: []
jobs:
- template: job-build-project.yml
parameters:
pool:
${{ if eq(variables['System.CollectionId'], 'cb55739e-4afe-46a3-970f-1b49d8ee7564') }}:
name: SHINE-INT-L
${{ else }}:
name: SHINE-OSS-L
${{ if eq(parameters.useVSPreview, true) }}:
demands: ImageOverride -equals SHINE-VS17-Preview
buildPlatforms:
- ${{ platform }}
buildConfigurations: [Release]
enablePackageCaching: true
enableMsBuildCaching: ${{ parameters.enableMsBuildCaching }}
runTests: false
buildTests: true
useVSPreview: ${{ parameters.useVSPreview }}
- ${{ if eq(parameters.useLatestOfficialBuild, false) }}:
- stage: Build_${{ platform }}
displayName: Build ${{ platform }}
dependsOn: []
jobs:
- template: job-build-project.yml
parameters:
pool:
${{ if eq(variables['System.CollectionId'], 'cb55739e-4afe-46a3-970f-1b49d8ee7564') }}:
name: SHINE-INT-L
${{ else }}:
name: SHINE-OSS-L
${{ if eq(parameters.useVSPreview, true) }}:
demands: ImageOverride -equals SHINE-VS17-Preview
buildPlatforms:
- ${{ platform }}
buildConfigurations: [Release]
enablePackageCaching: true
enableMsBuildCaching: ${{ parameters.enableMsBuildCaching }}
runTests: false
buildTests: true
useVSPreview: ${{ parameters.useVSPreview }}
timeoutInMinutes: 90
- ${{ if eq(parameters.useLatestOfficialBuild, true) }}:
- stage: BuildUITests_${{ platform }}
displayName: Build UI Tests Only
dependsOn: []
jobs:
- template: job-build-ui-tests.yml
parameters:
pool:
${{ if eq(variables['System.CollectionId'], 'cb55739e-4afe-46a3-970f-1b49d8ee7564') }}:
name: SHINE-INT-L
${{ else }}:
name: SHINE-OSS-L
${{ if eq(parameters.useVSPreview, true) }}:
demands: ImageOverride -equals SHINE-VS17-Preview
buildPlatforms:
- ${{ platform }}
uiTestModules: ${{ parameters.uiTestModules }}
- ${{ if eq(platform, 'x64') }}:
- stage: Test_x64Win10
displayName: Test x64Win10
dependsOn:
- Build_${{platform}}
${{ if eq(parameters.useLatestOfficialBuild, true) }}:
dependsOn:
- BuildUITests_${{ platform }}
${{ else }}:
dependsOn:
- Build_${{ platform }}
jobs:
- template: job-test-project.yml
parameters:
platform: x64Win10
configuration: Release
useLatestWebView2: ${{ parameters.useLatestWebView2 }}
useLatestOfficialBuild: ${{ parameters.useLatestOfficialBuild }}
useCurrentBranchBuild: ${{ parameters.useCurrentBranchBuild }}
uiTestModules: ${{ parameters.uiTestModules }}
# Additional per-user installation test (when both modes are enabled)
- ${{ if and(eq(parameters.useLatestOfficialBuild, true), eq(parameters.testBothInstallModes, true)) }}:
- template: job-test-project.yml
parameters:
platform: x64Win10
configuration: Release
useLatestWebView2: ${{ parameters.useLatestWebView2 }}
useLatestOfficialBuild: ${{ parameters.useLatestOfficialBuild }}
useCurrentBranchBuild: ${{ parameters.useCurrentBranchBuild }}
uiTestModules: ${{ parameters.uiTestModules }}
installMode: 'peruser'
jobSuffix: '_PerUser'
- ${{ if eq(platform, 'x64') }}:
- stage: Test_x64Win11
displayName: Test x64Win11
dependsOn:
- Build_${{platform}}
${{ if eq(parameters.useLatestOfficialBuild, true) }}:
dependsOn:
- BuildUITests_${{ platform }}
${{ else }}:
dependsOn:
- Build_${{ platform }}
jobs:
- template: job-test-project.yml
parameters:
platform: x64Win11
configuration: Release
useLatestWebView2: ${{ parameters.useLatestWebView2 }}
useLatestOfficialBuild: ${{ parameters.useLatestOfficialBuild }}
useCurrentBranchBuild: ${{ parameters.useCurrentBranchBuild }}
uiTestModules: ${{ parameters.uiTestModules }}
# Additional per-user installation test (when both modes are enabled)
- ${{ if and(eq(parameters.useLatestOfficialBuild, true), eq(parameters.testBothInstallModes, true)) }}:
- template: job-test-project.yml
parameters:
platform: x64Win11
configuration: Release
useLatestWebView2: ${{ parameters.useLatestWebView2 }}
useLatestOfficialBuild: ${{ parameters.useLatestOfficialBuild }}
useCurrentBranchBuild: ${{ parameters.useCurrentBranchBuild }}
uiTestModules: ${{ parameters.uiTestModules }}
installMode: 'peruser'
jobSuffix: '_PerUser'
- ${{ if ne(platform, 'x64') }}:
- stage: Test_${{ platform }}
displayName: Test ${{ platform }}
dependsOn:
- Build_${{platform}}
${{ if eq(parameters.useLatestOfficialBuild, true) }}:
dependsOn:
- BuildUITests_${{ platform }}
${{ else }}:
dependsOn:
- Build_${{ platform }}
jobs:
- template: job-test-project.yml
parameters:
platform: ${{ platform }}
configuration: Release
useLatestWebView2: ${{ parameters.useLatestWebView2 }}
useLatestOfficialBuild: ${{ parameters.useLatestOfficialBuild }}
useCurrentBranchBuild: ${{ parameters.useCurrentBranchBuild }}
uiTestModules: ${{ parameters.uiTestModules }}
# Additional per-user installation test (when both modes are enabled)
- ${{ if and(eq(parameters.useLatestOfficialBuild, true), eq(parameters.testBothInstallModes, true)) }}:
- template: job-test-project.yml
parameters:
platform: ${{ platform }}
configuration: Release
useLatestWebView2: ${{ parameters.useLatestWebView2 }}
useLatestOfficialBuild: ${{ parameters.useLatestOfficialBuild }}
useCurrentBranchBuild: ${{ parameters.useCurrentBranchBuild }}
uiTestModules: ${{ parameters.uiTestModules }}
installMode: 'peruser'
jobSuffix: '_PerUser'

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

@@ -383,6 +383,18 @@ _If you want to find diagnostic data events in the source code, these two links
<td>Microsoft.PowerToys.GcodeFilePreviewError</td>
<td>Triggered when there is an error previewing a G-code file.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.BgcodeFileHandlerLoaded</td>
<td>Triggered when a Binary G-code file handler is loaded.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.BgcodeFilePreviewed</td>
<td>Occurs when a Binary G-code file is previewed in File Explorer.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.BgcodeFilePreviewError</td>
<td>Triggered when there is an error previewing a Binary G-code file.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.MarkdownFileHandlerLoaded</td>
<td>Occurs when a Markdown file handler is loaded.</td>

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

@@ -714,6 +714,18 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CmdPalKeyboardService", "sr
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PowerRename.FuzzingTest", "src\modules\powerrename\PowerRename.FuzzingTest\PowerRename.FuzzingTest.vcxproj", "{2694E2FB-DCD5-4BFF-A418-B6C3C7CE3B8E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BgcodePreviewHandler", "src\modules\previewpane\BgcodePreviewHandler\BgcodePreviewHandler.csproj", "{9E0CBC06-F29A-4810-B93C-97D53863B95E}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "BgcodePreviewHandlerCpp", "src\modules\previewpane\BgcodePreviewHandlerCpp\BgcodePreviewHandlerCpp.vcxproj", "{F6088A11-1C9E-4420-AA90-CF7E78DD7F1C}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "BgcodeThumbnailProviderCpp", "src\modules\previewpane\BgcodeThumbnailProviderCpp\BgcodeThumbnailProviderCpp.vcxproj", "{47B0678C-806B-4FE1-9F50-46BA88989532}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BgcodeThumbnailProvider", "src\modules\previewpane\BgcodeThumbnailProvider\BgcodeThumbnailProvider.csproj", "{9BC1C986-1E97-4D07-A7B1-CE226C239EFA}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTests-BgcodePreviewHandler", "src\modules\previewpane\UnitTests-BgcodePreviewHandler\UnitTests-BgcodePreviewHandler.csproj", "{99CA1509-FB73-456E-AFAF-AB89C017BD72}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTests-BgcodeThumbnailProvider", "src\modules\previewpane\UnitTests-BgcodeThumbnailProvider\UnitTests-BgcodeThumbnailProvider.csproj", "{61CBF221-9452-4934-B685-146285E080D7}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MouseUtils.UITests", "src\modules\MouseUtils\MouseUtils.UITests\MouseUtils.UITests.csproj", "{4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkspacesEditorUITest", "src\modules\Workspaces\WorkspacesEditorUITest\WorkspacesEditorUITest.csproj", "{43E779F3-D83C-48B1-BA8D-1912DBD76FC9}"
@@ -2626,6 +2638,54 @@ Global
{2694E2FB-DCD5-4BFF-A418-B6C3C7CE3B8E}.Release|ARM64.ActiveCfg = Release|ARM64
{2694E2FB-DCD5-4BFF-A418-B6C3C7CE3B8E}.Release|x64.ActiveCfg = Release|x64
{2694E2FB-DCD5-4BFF-A418-B6C3C7CE3B8E}.Release|x64.Build.0 = Release|x64
{9E0CBC06-F29A-4810-B93C-97D53863B95E}.Debug|ARM64.ActiveCfg = Debug|ARM64
{9E0CBC06-F29A-4810-B93C-97D53863B95E}.Debug|ARM64.Build.0 = Debug|ARM64
{9E0CBC06-F29A-4810-B93C-97D53863B95E}.Debug|x64.ActiveCfg = Debug|x64
{9E0CBC06-F29A-4810-B93C-97D53863B95E}.Debug|x64.Build.0 = Debug|x64
{9E0CBC06-F29A-4810-B93C-97D53863B95E}.Release|ARM64.ActiveCfg = Release|ARM64
{9E0CBC06-F29A-4810-B93C-97D53863B95E}.Release|ARM64.Build.0 = Release|ARM64
{9E0CBC06-F29A-4810-B93C-97D53863B95E}.Release|x64.ActiveCfg = Release|x64
{9E0CBC06-F29A-4810-B93C-97D53863B95E}.Release|x64.Build.0 = Release|x64
{F6088A11-1C9E-4420-AA90-CF7E78DD7F1C}.Debug|ARM64.ActiveCfg = Debug|ARM64
{F6088A11-1C9E-4420-AA90-CF7E78DD7F1C}.Debug|ARM64.Build.0 = Debug|ARM64
{F6088A11-1C9E-4420-AA90-CF7E78DD7F1C}.Debug|x64.ActiveCfg = Debug|x64
{F6088A11-1C9E-4420-AA90-CF7E78DD7F1C}.Debug|x64.Build.0 = Debug|x64
{F6088A11-1C9E-4420-AA90-CF7E78DD7F1C}.Release|ARM64.ActiveCfg = Release|ARM64
{F6088A11-1C9E-4420-AA90-CF7E78DD7F1C}.Release|ARM64.Build.0 = Release|ARM64
{F6088A11-1C9E-4420-AA90-CF7E78DD7F1C}.Release|x64.ActiveCfg = Release|x64
{F6088A11-1C9E-4420-AA90-CF7E78DD7F1C}.Release|x64.Build.0 = Release|x64
{47B0678C-806B-4FE1-9F50-46BA88989532}.Debug|ARM64.ActiveCfg = Debug|ARM64
{47B0678C-806B-4FE1-9F50-46BA88989532}.Debug|ARM64.Build.0 = Debug|ARM64
{47B0678C-806B-4FE1-9F50-46BA88989532}.Debug|x64.ActiveCfg = Debug|x64
{47B0678C-806B-4FE1-9F50-46BA88989532}.Debug|x64.Build.0 = Debug|x64
{47B0678C-806B-4FE1-9F50-46BA88989532}.Release|ARM64.ActiveCfg = Release|ARM64
{47B0678C-806B-4FE1-9F50-46BA88989532}.Release|ARM64.Build.0 = Release|ARM64
{47B0678C-806B-4FE1-9F50-46BA88989532}.Release|x64.ActiveCfg = Release|x64
{47B0678C-806B-4FE1-9F50-46BA88989532}.Release|x64.Build.0 = Release|x64
{9BC1C986-1E97-4D07-A7B1-CE226C239EFA}.Debug|ARM64.ActiveCfg = Debug|ARM64
{9BC1C986-1E97-4D07-A7B1-CE226C239EFA}.Debug|ARM64.Build.0 = Debug|ARM64
{9BC1C986-1E97-4D07-A7B1-CE226C239EFA}.Debug|x64.ActiveCfg = Debug|x64
{9BC1C986-1E97-4D07-A7B1-CE226C239EFA}.Debug|x64.Build.0 = Debug|x64
{9BC1C986-1E97-4D07-A7B1-CE226C239EFA}.Release|ARM64.ActiveCfg = Release|ARM64
{9BC1C986-1E97-4D07-A7B1-CE226C239EFA}.Release|ARM64.Build.0 = Release|ARM64
{9BC1C986-1E97-4D07-A7B1-CE226C239EFA}.Release|x64.ActiveCfg = Release|x64
{9BC1C986-1E97-4D07-A7B1-CE226C239EFA}.Release|x64.Build.0 = Release|x64
{99CA1509-FB73-456E-AFAF-AB89C017BD72}.Debug|ARM64.ActiveCfg = Debug|ARM64
{99CA1509-FB73-456E-AFAF-AB89C017BD72}.Debug|ARM64.Build.0 = Debug|ARM64
{99CA1509-FB73-456E-AFAF-AB89C017BD72}.Debug|x64.ActiveCfg = Debug|x64
{99CA1509-FB73-456E-AFAF-AB89C017BD72}.Debug|x64.Build.0 = Debug|x64
{99CA1509-FB73-456E-AFAF-AB89C017BD72}.Release|ARM64.ActiveCfg = Release|ARM64
{99CA1509-FB73-456E-AFAF-AB89C017BD72}.Release|ARM64.Build.0 = Release|ARM64
{99CA1509-FB73-456E-AFAF-AB89C017BD72}.Release|x64.ActiveCfg = Release|x64
{99CA1509-FB73-456E-AFAF-AB89C017BD72}.Release|x64.Build.0 = Release|x64
{61CBF221-9452-4934-B685-146285E080D7}.Debug|ARM64.ActiveCfg = Debug|ARM64
{61CBF221-9452-4934-B685-146285E080D7}.Debug|ARM64.Build.0 = Debug|ARM64
{61CBF221-9452-4934-B685-146285E080D7}.Debug|x64.ActiveCfg = Debug|x64
{61CBF221-9452-4934-B685-146285E080D7}.Debug|x64.Build.0 = Debug|x64
{61CBF221-9452-4934-B685-146285E080D7}.Release|ARM64.ActiveCfg = Release|ARM64
{61CBF221-9452-4934-B685-146285E080D7}.Release|ARM64.Build.0 = Release|ARM64
{61CBF221-9452-4934-B685-146285E080D7}.Release|x64.ActiveCfg = Release|x64
{61CBF221-9452-4934-B685-146285E080D7}.Release|x64.Build.0 = Release|x64
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}.Debug|ARM64.ActiveCfg = Debug|ARM64
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}.Debug|ARM64.Build.0 = Debug|ARM64
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}.Debug|x64.ActiveCfg = Debug|x64
@@ -2940,6 +3000,12 @@ Global
{0217E86E-3476-9946-DE8E-9D200CEBD47A} = {D1D6BC88-09AE-4FB4-AD24-5DED46A791DD}
{5F63C743-F6CE-4DBA-A200-2B3F8A14E8C2} = {3846508C-77EB-4034-A702-F8BB263C4F79}
{2694E2FB-DCD5-4BFF-A418-B6C3C7CE3B8E} = {89E20BCE-EB9C-46C8-8B50-E01A82E6FDC3}
{9E0CBC06-F29A-4810-B93C-97D53863B95E} = {2F305555-C296-497E-AC20-5FA1B237996A}
{F6088A11-1C9E-4420-AA90-CF7E78DD7F1C} = {2F305555-C296-497E-AC20-5FA1B237996A}
{47B0678C-806B-4FE1-9F50-46BA88989532} = {2F305555-C296-497E-AC20-5FA1B237996A}
{9BC1C986-1E97-4D07-A7B1-CE226C239EFA} = {2F305555-C296-497E-AC20-5FA1B237996A}
{99CA1509-FB73-456E-AFAF-AB89C017BD72} = {2F305555-C296-497E-AC20-5FA1B237996A}
{61CBF221-9452-4934-B685-146285E080D7} = {2F305555-C296-497E-AC20-5FA1B237996A}
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A1} = {322566EF-20DC-43A6-B9F8-616AF942579A}
{43E779F3-D83C-48B1-BA8D-1912DBD76FC9} = {A2221D7E-55E7-4BEA-90D1-4F162D670BBF}
{2CF78CF7-8FEB-4BE1-9591-55FA25B48FC6} = {1AFB6476-670D-4E80-A464-657E01DFF482}

View File

@@ -37,17 +37,17 @@ Go to the [Microsoft PowerToys GitHub releases page][github-release-link] and cl
<!-- items that need to be updated release to release -->
[github-next-release-work]: https://github.com/microsoft/PowerToys/issues?q=is%3Aissue+milestone%3A%22PowerToys+0.93%22
[github-current-release-work]: https://github.com/microsoft/PowerToys/issues?q=is%3Aissue+milestone%3A%22PowerToys+0.92%22
[ptUserX64]: https://github.com/microsoft/PowerToys/releases/download/v0.92.0/PowerToysUserSetup-0.92.0-x64.exe
[ptUserArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.92.0/PowerToysUserSetup-0.92.0-arm64.exe
[ptMachineX64]: https://github.com/microsoft/PowerToys/releases/download/v0.92.0/PowerToysSetup-0.92.0-x64.exe
[ptMachineArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.92.0/PowerToysSetup-0.92.0-arm64.exe
[ptUserX64]: https://github.com/microsoft/PowerToys/releases/download/v0.92.1/PowerToysUserSetup-0.92.1-x64.exe
[ptUserArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.92.1/PowerToysUserSetup-0.92.1-arm64.exe
[ptMachineX64]: https://github.com/microsoft/PowerToys/releases/download/v0.92.1/PowerToysSetup-0.92.1-x64.exe
[ptMachineArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.92.1/PowerToysSetup-0.92.1-arm64.exe
| Description | Filename |
|----------------|----------|
| Per user - x64 | [PowerToysUserSetup-0.92.0-x64.exe][ptUserX64] |
| Per user - ARM64 | [PowerToysUserSetup-0.92.0-arm64.exe][ptUserArm64] |
| Machine wide - x64 | [PowerToysSetup-0.92.0-x64.exe][ptMachineX64] |
| Machine wide - ARM64 | [PowerToysSetup-0.92.0-arm64.exe][ptMachineArm64] |
| Per user - x64 | [PowerToysUserSetup-0.92.1-x64.exe][ptUserX64] |
| Per user - ARM64 | [PowerToysUserSetup-0.92.1-arm64.exe][ptUserArm64] |
| Machine wide - x64 | [PowerToysSetup-0.92.1-x64.exe][ptMachineX64] |
| Machine wide - ARM64 | [PowerToysSetup-0.92.1-arm64.exe][ptMachineArm64] |
This is our preferred method.

View File

@@ -16,6 +16,56 @@
- Run tests in the Test Explorer (`Test > Test Explorer` or `Ctrl+E, T`).
## Running tests in pipeline
The PowerToys UI test pipeline provides flexible options for building and testing:
### Pipeline Options
- **useLatestOfficialBuild**: When checked, downloads the latest official PowerToys build and installs it for testing. This skips the full solution build and only builds UI test projects.
- **useCurrentBranchBuild**: When checked along with `useLatestOfficialBuild`, downloads the official build from the current branch instead of main.
**Default value**: `false` (downloads from main branch)
**When to use this**:
- **Default scenario**: The pipeline tests against the latest signed PowerToys build from the `main` branch, regardless of which branch your test code changes are from
- **Custom branch testing**: Only specify `true` when:
- Your branch has produced its own signed PowerToys build via the official build pipeline
- You want to test against that specific branch's PowerToys build instead of main
- You are testing PowerToys functionality changes that are only available in your branch's build
**Important notes**:
- The test pipeline itself runs from your specified branch, but by default tests against the main branch's PowerToys build
- Not all branches have signed builds available - only use this if you're certain your branch has a signed build
- If enabled but no build exists for your branch, the pipeline may fail or fall back to main
- **uiTestModules**: Specify which UI test modules to build and run. This parameter controls both the `.csproj` projects to build and the `.dll` test assemblies to execute. Examples:
- `['UITests-FancyZones']` - Only FancyZones UI tests
- `['MouseUtils.UITests']` - Only MouseUtils UI tests
- `['UITests-FancyZones', 'MouseUtils.UITests']` - Multiple specific modules
- Leave empty to build and run all UI test modules
**Important**: The `uiTestModules` parameter values must match both the test project names (for `.csproj` selection during build) and the test assembly names (for `.dll` execution during testing).
### Build Modes
1. **Official Build + Selective Testing** (`useLatestOfficialBuild = true`)
- Downloads and installs official PowerToys build
- Builds only specified UI test projects
- Runs specified UI tests against installed PowerToys
- Controlled by `uiTestModules` parameter
2. **Full Build + Testing** (`useLatestOfficialBuild = false`)
- Builds entire PowerToys solution
- Builds UI test projects (all or specific based on `uiTestModules`)
- Runs UI tests (all or specific based on `uiTestModules`)
- Uses freshly built PowerToys for testing
> **Note**: Both modes support the `uiTestModules` parameter to control which specific UI test modules to build and run.
### Pipeline Access
- Pipeline: https://microsoft.visualstudio.com/Dart/_build?definitionId=161438&_a=summary
## How to add the first UI tests for your modules

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

@@ -18,6 +18,7 @@ This script checks the preview handler registration for the following file types
* .svgz
* .pdf
* .gcode
* .bgcode
* .stl
* .txt
* .ini

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

@@ -458,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

@@ -1170,7 +1170,7 @@ UINT __stdcall TerminateProcessesCA(MSIHANDLE hInstall)
}
processes.resize(bytes / sizeof(processes[0]));
std::array<std::wstring_view, 39> processesToTerminate = {
std::array<std::wstring_view, 41> processesToTerminate = {
L"PowerToys.PowerLauncher.exe",
L"PowerToys.Settings.exe",
L"PowerToys.AdvancedPaste.exe",
@@ -1186,12 +1186,14 @@ UINT __stdcall TerminateProcessesCA(MSIHANDLE hInstall)
L"PowerToys.PowerRename.exe",
L"PowerToys.ImageResizer.exe",
L"PowerToys.GcodeThumbnailProvider.exe",
L"PowerToys.BgcodeThumbnailProvider.exe",
L"PowerToys.PdfThumbnailProvider.exe",
L"PowerToys.MonacoPreviewHandler.exe",
L"PowerToys.MarkdownPreviewHandler.exe",
L"PowerToys.StlThumbnailProvider.exe",
L"PowerToys.SvgThumbnailProvider.exe",
L"PowerToys.GcodePreviewHandler.exe",
L"PowerToys.BgcodePreviewHandler.exe",
L"PowerToys.QoiPreviewHandler.exe",
L"PowerToys.PdfPreviewHandler.exe",
L"PowerToys.QoiThumbnailProvider.exe",

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

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.MSBuildCache.AzurePipelines" version="0.1.283-preview" />
<package id="Microsoft.MSBuildCache.Local" version="0.1.283-preview" />
<package id="Microsoft.MSBuildCache.SharedCompilation" version="0.1.283-preview" />
<package id="Microsoft.MSBuildCache.AzurePipelines" version="0.1.318-preview" />
<package id="Microsoft.MSBuildCache.Local" version="0.1.318-preview" />
<package id="Microsoft.MSBuildCache.SharedCompilation" version="0.1.318-preview" />
</packages>

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

@@ -0,0 +1,16 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace Microsoft.PowerToys.FilePreviewCommon
{
public enum BgcodeBlockType
{
FileMetadataBlock = 0,
GCodeBlock = 1,
SlicerMetadataBlock = 2,
PrinterMetadataBlock = 3,
PrintMetadataBlock = 4,
ThumbnailBlock = 5,
}
}

View File

@@ -0,0 +1,12 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace Microsoft.PowerToys.FilePreviewCommon
{
public enum BgcodeChecksumType
{
None = 0,
CRC32 = 1,
}
}

View File

@@ -0,0 +1,14 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace Microsoft.PowerToys.FilePreviewCommon
{
public enum BgcodeCompressionType
{
NoCompression = 0,
DeflateAlgorithm = 1,
HeatshrinkAlgorithm11 = 2,
HeatshrinkAlgorithm12 = 3,
}
}

View File

@@ -0,0 +1,129 @@
// 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.IO;
using System.IO.Compression;
using System.Linq;
namespace Microsoft.PowerToys.FilePreviewCommon
{
/// <summary>
/// Bgcode file helper class.
/// </summary>
public static class BgcodeHelper
{
private const uint MagicNumber = 'G' | 'C' << 8 | 'D' << 16 | 'E' << 24;
/// <summary>
/// Gets any thumbnails found in a bgcode file.
/// </summary>
/// <param name="reader">The <see cref="BinaryReader"/> instance to the bgcode file.</param>
/// <returns>The thumbnails found in a bgcode file.</returns>
public static IEnumerable<BgcodeThumbnail> GetThumbnails(BinaryReader reader)
{
var magicNumber = reader.ReadUInt32();
if (magicNumber != MagicNumber)
{
throw new InvalidDataException("Invalid magic number.");
}
var version = reader.ReadUInt32();
if (version != 1)
{
// Version 1 is the only one that exists
throw new InvalidDataException("Unsupported version.");
}
var checksum = (BgcodeChecksumType)reader.ReadUInt16();
while (reader.BaseStream.Position < reader.BaseStream.Length)
{
var blockType = (BgcodeBlockType)reader.ReadUInt16();
var compression = (BgcodeCompressionType)reader.ReadUInt16();
var uncompressedSize = reader.ReadUInt32();
var size = compression == BgcodeCompressionType.NoCompression ? uncompressedSize : reader.ReadUInt32();
switch (blockType)
{
case BgcodeBlockType.FileMetadataBlock:
case BgcodeBlockType.PrinterMetadataBlock:
case BgcodeBlockType.PrintMetadataBlock:
case BgcodeBlockType.SlicerMetadataBlock:
case BgcodeBlockType.GCodeBlock:
reader.BaseStream.Seek(2 + size, SeekOrigin.Current); // Skip
break;
case BgcodeBlockType.ThumbnailBlock:
var format = (BgcodeThumbnailFormat)reader.ReadUInt16();
reader.BaseStream.Seek(4, SeekOrigin.Current); // Skip width and height
var data = ReadAndDecompressData(reader, compression, (int)size);
if (data != null)
{
yield return new BgcodeThumbnail(format, data);
}
break;
}
if (checksum == BgcodeChecksumType.CRC32)
{
reader.BaseStream.Seek(4, SeekOrigin.Current); // Skip checksum
}
}
}
/// <summary>
/// Gets the best thumbnail available in a bgcode file.
/// </summary>
/// <param name="reader">The <see cref="BinaryReader"/> instance to the gcode file.</param>
/// <returns>The best thumbnail available in the gcode file.</returns>
public static BgcodeThumbnail? GetBestThumbnail(BinaryReader reader)
{
return GetThumbnails(reader)
.OrderByDescending(x => x.Format switch
{
BgcodeThumbnailFormat.PNG => 2,
BgcodeThumbnailFormat.QOI => 1,
BgcodeThumbnailFormat.JPG => 0,
_ => 0,
})
.ThenByDescending(x => x.Data.Length)
.FirstOrDefault();
}
private static byte[]? ReadAndDecompressData(BinaryReader reader, BgcodeCompressionType compression, int size)
{
// Though the spec doesn't actually mention it, the reference encoder code never applies compression to thumbnails data
// which makes complete sense as this data is PNG, JPEG or QOI encoded so already compressed as much as possible!
switch (compression)
{
case BgcodeCompressionType.NoCompression:
return reader.ReadBytes(size);
case BgcodeCompressionType.DeflateAlgorithm:
var buffer = new byte[size];
using (var deflateStream = new DeflateStream(reader.BaseStream, CompressionMode.Decompress, true))
{
deflateStream.ReadExactly(buffer, 0, size);
}
return buffer;
default:
reader.BaseStream.Seek(size, SeekOrigin.Current); // Skip unknown or unsupported compression types
return null;
}
}
}
}

View File

@@ -0,0 +1,66 @@
// 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.Drawing;
using System.IO;
namespace Microsoft.PowerToys.FilePreviewCommon
{
/// <summary>
/// Represents a bgcode thumbnail.
/// </summary>
public class BgcodeThumbnail
{
/// <summary>
/// Gets the bgcode thumbnail image format.
/// </summary>
public BgcodeThumbnailFormat Format { get; }
/// <summary>
/// Gets the bgcode thumbnail image data.
/// </summary>
public byte[] Data { get; }
/// <summary>
/// Initializes a new instance of the <see cref="BgcodeThumbnail"/> class.
/// </summary>
/// <param name="format">The bgcode thumbnail image format.</param>
/// <param name="data">The bgcode thumbnail image data.</param>
public BgcodeThumbnail(BgcodeThumbnailFormat format, byte[] data)
{
Format = format;
Data = data;
}
/// <summary>
/// Gets a <see cref="Bitmap"/> representing this thumbnail.
/// </summary>
/// <returns>A <see cref="Bitmap"/> representing this thumbnail.</returns>
public Bitmap? GetBitmap()
{
switch (Format)
{
case BgcodeThumbnailFormat.JPG:
case BgcodeThumbnailFormat.PNG:
return BitmapFromByteArray();
case BgcodeThumbnailFormat.QOI:
return BitmapFromQoiByteArray();
default:
return null;
}
}
private Bitmap BitmapFromByteArray()
{
return new Bitmap(new MemoryStream(Data));
}
private Bitmap BitmapFromQoiByteArray()
{
return QoiImage.FromStream(new MemoryStream(Data));
}
}
}

View File

@@ -0,0 +1,24 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace Microsoft.PowerToys.FilePreviewCommon
{
public enum BgcodeThumbnailFormat
{
/// <summary>
/// PNG image format.
/// </summary>
PNG = 0,
/// <summary>
/// JPG image format.
/// </summary>
JPG = 1,
/// <summary>
/// QOI image format.
/// </summary>
QOI = 2,
}
}

View File

@@ -56,6 +56,10 @@ namespace winrt::PowerToys::GPOWrapper::implementation
{
return static_cast<GpoRuleConfigured>(powertoys_gpo::getConfiguredGcodePreviewEnabledValue());
}
GpoRuleConfigured GPOWrapper::GetConfiguredBgcodePreviewEnabledValue()
{
return static_cast<GpoRuleConfigured>(powertoys_gpo::getConfiguredBgcodePreviewEnabledValue());
}
GpoRuleConfigured GPOWrapper::GetConfiguredSvgThumbnailsEnabledValue()
{
return static_cast<GpoRuleConfigured>(powertoys_gpo::getConfiguredSvgThumbnailsEnabledValue());
@@ -68,6 +72,10 @@ namespace winrt::PowerToys::GPOWrapper::implementation
{
return static_cast<GpoRuleConfigured>(powertoys_gpo::getConfiguredGcodeThumbnailsEnabledValue());
}
GpoRuleConfigured GPOWrapper::GetConfiguredBgcodeThumbnailsEnabledValue()
{
return static_cast<GpoRuleConfigured>(powertoys_gpo::getConfiguredBgcodeThumbnailsEnabledValue());
}
GpoRuleConfigured GPOWrapper::GetConfiguredStlThumbnailsEnabledValue()
{
return static_cast<GpoRuleConfigured>(powertoys_gpo::getConfiguredStlThumbnailsEnabledValue());

View File

@@ -21,9 +21,11 @@ namespace winrt::PowerToys::GPOWrapper::implementation
static GpoRuleConfigured GetConfiguredMouseWithoutBordersEnabledValue();
static GpoRuleConfigured GetConfiguredPdfPreviewEnabledValue();
static GpoRuleConfigured GetConfiguredGcodePreviewEnabledValue();
static GpoRuleConfigured GetConfiguredBgcodePreviewEnabledValue();
static GpoRuleConfigured GetConfiguredSvgThumbnailsEnabledValue();
static GpoRuleConfigured GetConfiguredPdfThumbnailsEnabledValue();
static GpoRuleConfigured GetConfiguredGcodeThumbnailsEnabledValue();
static GpoRuleConfigured GetConfiguredBgcodeThumbnailsEnabledValue();
static GpoRuleConfigured GetConfiguredStlThumbnailsEnabledValue();
static GpoRuleConfigured GetConfiguredHostsFileEditorEnabledValue();
static GpoRuleConfigured GetConfiguredImageResizerEnabledValue();

View File

@@ -24,9 +24,11 @@ namespace PowerToys
static GpoRuleConfigured GetConfiguredMonacoPreviewEnabledValue();
static GpoRuleConfigured GetConfiguredPdfPreviewEnabledValue();
static GpoRuleConfigured GetConfiguredGcodePreviewEnabledValue();
static GpoRuleConfigured GetConfiguredBgcodePreviewEnabledValue();
static GpoRuleConfigured GetConfiguredSvgThumbnailsEnabledValue();
static GpoRuleConfigured GetConfiguredPdfThumbnailsEnabledValue();
static GpoRuleConfigured GetConfiguredGcodeThumbnailsEnabledValue();
static GpoRuleConfigured GetConfiguredBgcodeThumbnailsEnabledValue();
static GpoRuleConfigured GetConfiguredStlThumbnailsEnabledValue();
static GpoRuleConfigured GetConfiguredHostsFileEditorEnabledValue();
static GpoRuleConfigured GetConfiguredImageResizerEnabledValue();

View File

@@ -77,7 +77,7 @@ namespace Microsoft.PowerToys.UITest
internal class ModuleConfigData
{
private Dictionary<PowerToysModule, string> ModulePath { get; }
private Dictionary<PowerToysModule, ModuleInfo> ModuleInfo { get; }
// Singleton instance of ModuleConfigData.
private static readonly Lazy<ModuleConfigData> SingletonInstance = new Lazy<ModuleConfigData>(() => new ModuleConfigData());
@@ -86,37 +86,74 @@ namespace Microsoft.PowerToys.UITest
public const string WindowsApplicationDriverUrl = "http://127.0.0.1:4723";
public Dictionary<PowerToysModule, string> ModuleWindowName { get; }
private bool UseInstallerForTest { get; }
private ModuleConfigData()
{
// The exe window name for each module.
ModuleWindowName = new Dictionary<PowerToysModule, string>
{
[PowerToysModule.PowerToysSettings] = "PowerToys Settings",
[PowerToysModule.FancyZone] = "FancyZones Layout",
[PowerToysModule.Hosts] = "Hosts File Editor",
[PowerToysModule.Runner] = "PowerToys",
[PowerToysModule.Workspaces] = "Workspaces Editor",
[PowerToysModule.PowerRename] = "PowerRename",
};
// Check if we should use installer paths from environment variable
string? useInstallerForTestEnv =
Environment.GetEnvironmentVariable("useInstallerForTest") ?? Environment.GetEnvironmentVariable("USEINSTALLERFORTEST");
UseInstallerForTest = !string.IsNullOrEmpty(useInstallerForTestEnv) && bool.TryParse(useInstallerForTestEnv, out bool result) && result;
// Exe start path for the module if it exists.
ModulePath = new Dictionary<PowerToysModule, string>
// Module information including executable name, window name, and optional subdirectory
ModuleInfo = new Dictionary<PowerToysModule, ModuleInfo>
{
[PowerToysModule.PowerToysSettings] = @"\..\..\..\WinUI3Apps\PowerToys.Settings.exe",
[PowerToysModule.FancyZone] = @"\..\..\..\PowerToys.FancyZonesEditor.exe",
[PowerToysModule.Hosts] = @"\..\..\..\WinUI3Apps\PowerToys.Hosts.exe",
[PowerToysModule.Runner] = @"\..\..\..\PowerToys.exe",
[PowerToysModule.Workspaces] = @"\..\..\..\PowerToys.WorkspacesEditor.exe",
[PowerToysModule.PowerRename] = @"\..\..\..\WinUI3Apps\PowerToys.PowerRename.exe",
[PowerToysModule.PowerToysSettings] = new ModuleInfo("PowerToys.Settings.exe", "PowerToys Settings", "WinUI3Apps"),
[PowerToysModule.FancyZone] = new ModuleInfo("PowerToys.FancyZonesEditor.exe", "FancyZones Layout"),
[PowerToysModule.Hosts] = new ModuleInfo("PowerToys.Hosts.exe", "Hosts File Editor", "WinUI3Apps"),
[PowerToysModule.Runner] = new ModuleInfo("PowerToys.exe", "PowerToys"),
[PowerToysModule.Workspaces] = new ModuleInfo("PowerToys.WorkspacesEditor.exe", "Workspaces Editor"),
[PowerToysModule.PowerRename] = new ModuleInfo("PowerToys.PowerRename.exe", "PowerRename", "WinUI3Apps"),
};
}
public string GetModulePath(PowerToysModule scope) => ModulePath[scope];
private string GetPowerToysInstallPath()
{
// Try common installation paths
string[] possiblePaths =
{
@"C:\Program Files\PowerToys",
@"C:\Program Files (x86)\PowerToys",
Environment.ExpandEnvironmentVariables(@"%LocalAppData%\PowerToys"),
Environment.ExpandEnvironmentVariables(@"%ProgramFiles%\PowerToys"),
};
foreach (string path in possiblePaths)
{
if (Directory.Exists(path) && File.Exists(Path.Combine(path, "PowerToys.exe")))
{
return path;
}
}
// Fallback to Program Files if not found
return @"C:\Program Files\PowerToys";
}
public string GetModulePath(PowerToysModule scope)
{
var moduleInfo = ModuleInfo[scope];
if (UseInstallerForTest)
{
string powerToysInstallPath = GetPowerToysInstallPath();
string installedPath = moduleInfo.GetInstalledPath(powerToysInstallPath);
if (File.Exists(installedPath))
{
return installedPath;
}
else
{
Console.WriteLine($"Warning: Installed module not found at {installedPath}, using development path");
}
}
return moduleInfo.GetDevelopmentPath();
}
public string GetWindowsApplicationDriverUrl() => WindowsApplicationDriverUrl;
public string GetModuleWindowName(PowerToysModule scope) => ModuleWindowName[scope];
public string GetModuleWindowName(PowerToysModule scope) => ModuleInfo[scope].WindowName;
}
}

View File

@@ -0,0 +1,54 @@
// 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.PowerToys.UITest
{
internal class ModuleInfo
{
public string ExecutableName { get; }
public string? SubDirectory { get; }
public string WindowName { get; }
public ModuleInfo(string executableName, string windowName, string? subDirectory = null)
{
ExecutableName = executableName;
WindowName = windowName;
SubDirectory = subDirectory;
}
/// <summary>
/// Gets the relative development path for this module
/// </summary>
public string GetDevelopmentPath()
{
if (string.IsNullOrEmpty(SubDirectory))
{
return $@"\..\..\..\{ExecutableName}";
}
return $@"\..\..\..\{SubDirectory}\{ExecutableName}";
}
/// <summary>
/// Gets the installed path for this module based on the PowerToys install directory
/// </summary>
public string GetInstalledPath(string powerToysInstallPath)
{
if (string.IsNullOrEmpty(SubDirectory))
{
return Path.Combine(powerToysInstallPath, ExecutableName);
}
return Path.Combine(powerToysInstallPath, SubDirectory, ExecutableName);
}
}
}

View File

@@ -37,13 +37,18 @@ namespace Microsoft.PowerToys.UITest
private PowerToysModule scope;
private string[]? commandLineArgs;
private bool UseInstallerForTest { get; }
[UnconditionalSuppressMessage("SingleFile", "IL3000:Avoid accessing Assembly file path when publishing as a single file", Justification = "<Pending>")]
public SessionHelper(PowerToysModule scope, string[]? commandLineArgs = null)
{
this.scope = scope;
this.commandLineArgs = commandLineArgs;
this.sessionPath = ModuleConfigData.Instance.GetModulePath(scope);
this.locationPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
string? useInstallerForTestEnv =
Environment.GetEnvironmentVariable("useInstallerForTest") ?? Environment.GetEnvironmentVariable("USEINSTALLERFORTEST");
UseInstallerForTest = !string.IsNullOrEmpty(useInstallerForTestEnv) && bool.TryParse(useInstallerForTestEnv, out bool result) && result;
this.locationPath = UseInstallerForTest ? string.Empty : Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
CheckWinAppDriverAndRoot();
}
@@ -156,7 +161,7 @@ namespace Microsoft.PowerToys.UITest
if (root != null)
{
const int maxRetries = 3;
const int maxRetries = 5;
const int delayMs = 5000;
var windowName = "PowerToys Settings";

View File

@@ -127,6 +127,10 @@ namespace winrt::PowerToys::Interop::implementation
{
return CommonSharedConstants::GCODE_PREVIEW_RESIZE_EVENT;
}
hstring Constants::BgcodePreviewResizeEvent()
{
return CommonSharedConstants::BGCODE_PREVIEW_RESIZE_EVENT;
}
hstring Constants::QoiPreviewResizeEvent()
{
return CommonSharedConstants::QOI_PREVIEW_RESIZE_EVENT;

View File

@@ -35,6 +35,7 @@ namespace winrt::PowerToys::Interop::implementation
static hstring RegistryPreviewTriggerEvent();
static hstring MeasureToolTriggerEvent();
static hstring GcodePreviewResizeEvent();
static hstring BgcodePreviewResizeEvent();
static hstring QoiPreviewResizeEvent();
static hstring DevFilesPreviewResizeEvent();
static hstring MarkdownPreviewResizeEvent();

View File

@@ -32,6 +32,7 @@ namespace PowerToys
static String RegistryPreviewTriggerEvent();
static String MeasureToolTriggerEvent();
static String GcodePreviewResizeEvent();
static String BgcodePreviewResizeEvent();
static String QoiPreviewResizeEvent();
static String DevFilesPreviewResizeEvent();
static String MarkdownPreviewResizeEvent();

View File

@@ -92,6 +92,9 @@ namespace CommonSharedConstants
// Path to the event used by GcodePreviewHandler
const wchar_t GCODE_PREVIEW_RESIZE_EVENT[] = L"Local\\PowerToysGcodePreviewResizeEvent-6ff1f9bd-ccbd-4b24-a79f-40a34fb0317d";
// Path to the event used by BgcodePreviewHandler
const wchar_t BGCODE_PREVIEW_RESIZE_EVENT[] = L"Local\\PowerToysBgcodePreviewResizeEvent-1a76a553-919a-49e0-8179-776582d8e476";
// Path to the event used by QoiPreviewHandler
const wchar_t QOI_PREVIEW_RESIZE_EVENT[] = L"Local\\PowerToysQoiPreviewResizeEvent-579518d1-8c8b-494f-8143-04f43d761ead";

View File

@@ -19,6 +19,10 @@ struct LogSettings
inline const static std::wstring gcodePrevLogPath = L"logs\\FileExplorer_localLow\\GcodePreviewHandler\\gcode-prev-handler-log.log";
inline const static std::string gcodeThumbLoggerName = "GcodeThumbnailProvider";
inline const static std::wstring gcodeThumbLogPath = L"logs\\FileExplorer_localLow\\GcodeThumbnailProvider\\gcode-thumbnail-provider-log.log";
inline const static std::string bgcodePrevLoggerName = "bgcodePrevHandler";
inline const static std::wstring bgcodePrevLogPath = L"logs\\FileExplorer_localLow\\BgcodePreviewHandler\\bgcode-prev-handler-log.log";
inline const static std::string bgcodeThumbLoggerName = "BgcodeThumbnailProvider";
inline const static std::wstring bgcodeThumbLogPath = L"logs\\FileExplorer_localLow\\BgcodeThumbnailProvider\\bgcode-thumbnail-provider-log.log";
inline const static std::string mdPrevLoggerName = "MDPrevHandler";
inline const static std::wstring mdPrevLogPath = L"logs\\FileExplorer_localLow\\MDPrevHandler\\md-prev-handler-log.log";
inline const static std::string monacoPrevLoggerName = "MonacoPrevHandler";

View File

@@ -37,9 +37,11 @@ namespace powertoys_gpo
const std::wstring POLICY_CONFIGURE_ENABLED_MONACO_PREVIEW = L"ConfigureEnabledUtilityFileExplorerMonacoPreview";
const std::wstring POLICY_CONFIGURE_ENABLED_PDF_PREVIEW = L"ConfigureEnabledUtilityFileExplorerPDFPreview";
const std::wstring POLICY_CONFIGURE_ENABLED_GCODE_PREVIEW = L"ConfigureEnabledUtilityFileExplorerGcodePreview";
const std::wstring POLICY_CONFIGURE_ENABLED_BGCODE_PREVIEW = L"ConfigureEnabledUtilityFileExplorerBgcodePreview";
const std::wstring POLICY_CONFIGURE_ENABLED_SVG_THUMBNAILS = L"ConfigureEnabledUtilityFileExplorerSVGThumbnails";
const std::wstring POLICY_CONFIGURE_ENABLED_PDF_THUMBNAILS = L"ConfigureEnabledUtilityFileExplorerPDFThumbnails";
const std::wstring POLICY_CONFIGURE_ENABLED_GCODE_THUMBNAILS = L"ConfigureEnabledUtilityFileExplorerGcodeThumbnails";
const std::wstring POLICY_CONFIGURE_ENABLED_BGCODE_THUMBNAILS = L"ConfigureEnabledUtilityFileExplorerBgcodeThumbnails";
const std::wstring POLICY_CONFIGURE_ENABLED_STL_THUMBNAILS = L"ConfigureEnabledUtilityFileExplorerSTLThumbnails";
const std::wstring POLICY_CONFIGURE_ENABLED_HOSTS_FILE_EDITOR = L"ConfigureEnabledUtilityHostsFileEditor";
const std::wstring POLICY_CONFIGURE_ENABLED_IMAGE_RESIZER = L"ConfigureEnabledUtilityImageResizer";
@@ -328,6 +330,11 @@ namespace powertoys_gpo
return getUtilityEnabledValue(POLICY_CONFIGURE_ENABLED_GCODE_PREVIEW);
}
inline gpo_rule_configured_t getConfiguredBgcodePreviewEnabledValue()
{
return getUtilityEnabledValue(POLICY_CONFIGURE_ENABLED_BGCODE_PREVIEW);
}
inline gpo_rule_configured_t getConfiguredSvgThumbnailsEnabledValue()
{
return getUtilityEnabledValue(POLICY_CONFIGURE_ENABLED_SVG_THUMBNAILS);
@@ -343,6 +350,11 @@ namespace powertoys_gpo
return getUtilityEnabledValue(POLICY_CONFIGURE_ENABLED_GCODE_THUMBNAILS);
}
inline gpo_rule_configured_t getConfiguredBgcodeThumbnailsEnabledValue()
{
return getUtilityEnabledValue(POLICY_CONFIGURE_ENABLED_BGCODE_THUMBNAILS);
}
inline gpo_rule_configured_t getConfiguredStlThumbnailsEnabledValue()
{
return getUtilityEnabledValue(POLICY_CONFIGURE_ENABLED_STL_THUMBNAILS);

View File

@@ -17,6 +17,7 @@ namespace NonLocalizable
const static std::vector<std::wstring> ExtMarkdown = { L".md", L".markdown", L".mdown", L".mkdn", L".mkd", L".mdwn", L".mdtxt", L".mdtext" };
const static std::vector<std::wstring> ExtPDF = { L".pdf" };
const static std::vector<std::wstring> ExtGCode = { L".gcode" };
const static std::vector<std::wstring> ExtBGCode = { L".bgcode" };
const static std::vector<std::wstring> ExtSTL = { L".stl" };
const static std::vector<std::wstring> ExtQOI = { L".qoi" };
const static std::vector<std::wstring> ExtNoNoNo = {
@@ -146,6 +147,19 @@ inline registry::ChangeSet getGcodePreviewHandlerChangeSet(const std::wstring in
NonLocalizable::ExtGCode);
}
inline registry::ChangeSet getBgcodePreviewHandlerChangeSet(const std::wstring installationDir, const bool perUser)
{
using namespace registry::shellex;
return generatePreviewHandler(PreviewHandlerType::preview,
perUser,
L"{0e6d5bdd-d5f8-4692-a089-8bb88cdd37f4}",
get_std_product_version(),
(fs::path{ installationDir } / LR"d(PowerToys.BgcodePreviewHandlerCpp.dll)d").wstring(),
L"BgcodePreviewHandler",
L"Binary G-code Preview Handler",
NonLocalizable::ExtBGCode);
}
inline registry::ChangeSet getQoiPreviewHandlerChangeSet(const std::wstring installationDir, const bool perUser)
{
using namespace registry::shellex;
@@ -200,6 +214,19 @@ inline registry::ChangeSet getGcodeThumbnailHandlerChangeSet(const std::wstring
NonLocalizable::ExtGCode);
}
inline registry::ChangeSet getBgcodeThumbnailHandlerChangeSet(const std::wstring installationDir, const bool perUser)
{
using namespace registry::shellex;
return generatePreviewHandler(PreviewHandlerType::thumbnail,
perUser,
L"{5c93a1e4-99d0-4fb3-991c-6c296a27be21}",
get_std_product_version(),
(fs::path{ installationDir } / LR"d(PowerToys.BgcodeThumbnailProviderCpp.dll)d").wstring(),
L"BgcodeThumbnailProvider",
L"Binary G-code Thumbnail Provider",
NonLocalizable::ExtBGCode);
}
inline registry::ChangeSet getStlThumbnailHandlerChangeSet(const std::wstring installationDir, const bool perUser)
{
using namespace registry::shellex;
@@ -275,9 +302,11 @@ inline std::vector<registry::ChangeSet> getAllOnByDefaultModulesChangeSets(const
getMdPreviewHandlerChangeSet(installationDir, PER_USER),
getMonacoPreviewHandlerChangeSet(installationDir, PER_USER),
getGcodePreviewHandlerChangeSet(installationDir, PER_USER),
getBgcodePreviewHandlerChangeSet(installationDir, PER_USER),
getQoiPreviewHandlerChangeSet(installationDir, PER_USER),
getSvgThumbnailHandlerChangeSet(installationDir, PER_USER),
getGcodeThumbnailHandlerChangeSet(installationDir, PER_USER),
getBgcodeThumbnailHandlerChangeSet(installationDir, PER_USER),
getStlThumbnailHandlerChangeSet(installationDir, PER_USER),
getQoiThumbnailHandlerChangeSet(installationDir, PER_USER),
getRegistryPreviewChangeSet(installationDir, PER_USER) };
@@ -291,10 +320,12 @@ inline std::vector<registry::ChangeSet> getAllModulesChangeSets(const std::wstri
getMonacoPreviewHandlerChangeSet(installationDir, PER_USER),
getPdfPreviewHandlerChangeSet(installationDir, PER_USER),
getGcodePreviewHandlerChangeSet(installationDir, PER_USER),
getBgcodePreviewHandlerChangeSet(installationDir, PER_USER),
getQoiPreviewHandlerChangeSet(installationDir, PER_USER),
getSvgThumbnailHandlerChangeSet(installationDir, PER_USER),
getPdfThumbnailHandlerChangeSet(installationDir, PER_USER),
getGcodeThumbnailHandlerChangeSet(installationDir, PER_USER),
getBgcodeThumbnailHandlerChangeSet(installationDir, PER_USER),
getStlThumbnailHandlerChangeSet(installationDir, PER_USER),
getQoiThumbnailHandlerChangeSet(installationDir, PER_USER),
getRegistryPreviewChangeSet(installationDir, PER_USER),

View File

@@ -54,6 +54,8 @@ properties:
EnablePdfThumbnail: false
EnableGcodePreview: false
EnableGcodeThumbnail: false
EnableBgcodePreview: false
EnableBgcodeThumbnail: false
EnableStlThumbnail: false
EnableQoiPreview: false
EnableQoiThumbnail: false

View File

@@ -54,6 +54,8 @@ properties:
EnablePdfThumbnail: true
EnableGcodePreview: true
EnableGcodeThumbnail: true
EnableBgcodePreview: true
EnableBgcodeThumbnail: true
EnableStlThumbnail: true
EnableQoiPreview: true
EnableQoiThumbnail: true

View File

@@ -217,6 +217,16 @@
<decimal value="0" />
</disabledValue>
</policy>
<policy name="ConfigureEnabledUtilityFileExplorerBgcodePreview" class="Both" displayName="$(string.ConfigureEnabledUtilityFileExplorerBgcodePreview)" explainText="$(string.ConfigureEnabledUtilityDescription)" key="Software\Policies\PowerToys" valueName="ConfigureEnabledUtilityFileExplorerBgcodePreview">
<parentCategory ref="PowerToys" />
<supportedOn ref="SUPPORTED_POWERTOYS_0_93_0" />
<enabledValue>
<decimal value="1" />
</enabledValue>
<disabledValue>
<decimal value="0" />
</disabledValue>
</policy>
<policy name="ConfigureEnabledUtilityFileExplorerSVGThumbnails" class="Both" displayName="$(string.ConfigureEnabledUtilityFileExplorerSVGThumbnails)" explainText="$(string.ConfigureEnabledUtilityDescription)" key="Software\Policies\PowerToys" valueName="ConfigureEnabledUtilityFileExplorerSVGThumbnails">
<parentCategory ref="PowerToys" />
<supportedOn ref="SUPPORTED_POWERTOYS_0_64_0" />
@@ -247,6 +257,16 @@
<decimal value="0" />
</disabledValue>
</policy>
<policy name="ConfigureEnabledUtilityFileExplorerBgcodeThumbnails" class="Both" displayName="$(string.ConfigureEnabledUtilityFileExplorerBgcodeThumbnails)" explainText="$(string.ConfigureEnabledUtilityDescription)" key="Software\Policies\PowerToys" valueName="ConfigureEnabledUtilityFileExplorerBgcodeThumbnails">
<parentCategory ref="PowerToys" />
<supportedOn ref="SUPPORTED_POWERTOYS_0_93_0" />
<enabledValue>
<decimal value="1" />
</enabledValue>
<disabledValue>
<decimal value="0" />
</disabledValue>
</policy>
<policy name="ConfigureEnabledUtilityFileExplorerQOIPreview" class="Both" displayName="$(string.ConfigureEnabledUtilityFileExplorerQOIPreview)" explainText="$(string.ConfigureEnabledUtilityDescription)" key="Software\Policies\PowerToys" valueName="ConfigureEnabledUtilityFileExplorerQOIPreview">
<parentCategory ref="PowerToys" />
<supportedOn ref="SUPPORTED_POWERTOYS_0_76_0" />

View File

@@ -253,9 +253,11 @@ If you don't configure this policy, the user will be able to control the setting
<string id="ConfigureEnabledUtilityFileExplorerMonacoPreview">Source code file preview: Configure enabled state</string>
<string id="ConfigureEnabledUtilityFileExplorerPDFPreview">PDF file preview: Configure enabled state</string>
<string id="ConfigureEnabledUtilityFileExplorerGcodePreview">Gcode file preview: Configure enabled state</string>
<string id="ConfigureEnabledUtilityFileExplorerBgcodePreview">BGcode file preview: Configure enabled state</string>
<string id="ConfigureEnabledUtilityFileExplorerSVGThumbnails">SVG file thumbnail: Configure enabled state</string>
<string id="ConfigureEnabledUtilityFileExplorerPDFThumbnails">PDF file thumbnail: Configure enabled state</string>
<string id="ConfigureEnabledUtilityFileExplorerGcodeThumbnails">Gcode file thumbnail: Configure enabled state</string>
<string id="ConfigureEnabledUtilityFileExplorerBgcodeThumbnails">BGcode file thumbnail: Configure enabled state</string>
<string id="ConfigureEnabledUtilityFileExplorerSTLThumbnails">STL file thumbnail: Configure enabled state</string>
<string id="ConfigureEnabledUtilityHostsFileEditor">Hosts file editor: Configure enabled state</string>
<string id="ConfigureEnabledUtilityImageResizer">Image Resizer: Configure enabled state</string>

View File

@@ -15,7 +15,7 @@
<RunVSTest>false</RunVSTest>
</PropertyGroup>
<PropertyGroup>
<OutputPath>$(SolutionDir)$(Platform)\$(Configuration)\tests\Hosts.UITests\</OutputPath>
<OutputPath>..\..\..\..\$(Platform)\$(Configuration)\tests\Hosts.UITests\</OutputPath>
</PropertyGroup>
<ItemGroup>
<EmbeddedResource Include="Baseline\HostModuleTests_TestAddingEntry_arm64.png" />

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

@@ -40,6 +40,8 @@ public sealed class CommandProviderWrapper
public CommandSettingsViewModel? Settings { get; private set; }
public bool IsActive { get; private set; }
public string ProviderId
{
get
@@ -124,12 +126,14 @@ public sealed class CommandProviderWrapper
{
if (!isValid)
{
IsActive = false;
return;
}
var settings = serviceProvider.GetService<SettingsModel>()!;
if (!GetProviderSettings(settings).IsEnabled)
IsActive = GetProviderSettings(settings).IsEnabled;
if (!IsActive)
{
return;
}
@@ -173,13 +177,13 @@ public sealed class CommandProviderWrapper
private void InitializeCommands(ICommandItem[] commands, IFallbackCommandItem[] fallbacks, IServiceProvider serviceProvider, WeakReference<IPageContext> pageContext)
{
var settings = serviceProvider.GetService<SettingsModel>()!;
var providerSettings = GetProviderSettings(settings);
Func<ICommandItem?, bool, TopLevelViewModel> makeAndAdd = (ICommandItem? i, bool fallback) =>
{
CommandItemViewModel commandItemViewModel = new(new(i), pageContext);
TopLevelViewModel topLevelViewModel = new(commandItemViewModel, fallback, ExtensionHost, ProviderId, settings, serviceProvider);
topLevelViewModel.ItemViewModel.SlowInitializeProperties();
TopLevelViewModel topLevelViewModel = new(commandItemViewModel, fallback, ExtensionHost, ProviderId, settings, providerSettings, serviceProvider);
topLevelViewModel.InitializeProperties();
return topLevelViewModel;
};

View File

@@ -13,7 +13,7 @@ internal sealed partial class FallbackLogItem : FallbackCommandItem
private readonly LogMessagesPage _logMessagesPage;
public FallbackLogItem()
: base(new LogMessagesPage(), Resources.builtin_log_subtitle)
: base(new LogMessagesPage() { Id = "com.microsoft.cmdpal.log" }, Resources.builtin_log_subtitle)
{
_logMessagesPage = (LogMessagesPage)Command!;
Title = string.Empty;

View File

@@ -12,7 +12,9 @@ internal sealed partial class FallbackReloadItem : FallbackCommandItem
private readonly ReloadExtensionsCommand _reloadCommand;
public FallbackReloadItem()
: base(new ReloadExtensionsCommand(), Properties.Resources.builtin_reload_display_title)
: base(
new ReloadExtensionsCommand() { Id = "com.microsoft.cmdpal.reload" },
Properties.Resources.builtin_reload_display_title)
{
_reloadCommand = (ReloadExtensionsCommand)Command!;
Title = string.Empty;

View File

@@ -5,6 +5,7 @@
using System.Collections.Immutable;
using System.Collections.Specialized;
using CommunityToolkit.Mvvm.Messaging;
using ManagedCommon;
using Microsoft.CmdPal.Ext.Apps;
using Microsoft.CmdPal.UI.ViewModels.Messages;
using Microsoft.CommandPalette.Extensions;
@@ -25,6 +26,8 @@ public partial class MainListPage : DynamicListPage,
private readonly TopLevelCommandManager _tlcManager;
private IEnumerable<IListItem>? _filteredItems;
private bool _includeApps;
private bool _filteredItemsIncludesApps;
public MainListPage(IServiceProvider serviceProvider)
{
@@ -64,7 +67,34 @@ public partial class MainListPage : DynamicListPage,
}
}
private void Commands_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) => RaiseItemsChanged(_tlcManager.TopLevelCommands.Count);
private void Commands_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
_includeApps = _tlcManager.IsProviderActive(AllAppsCommandProvider.WellKnownId);
if (_includeApps != _filteredItemsIncludesApps)
{
ReapplySearchInBackground();
}
else
{
RaiseItemsChanged(_tlcManager.TopLevelCommands.Count);
}
}
private void ReapplySearchInBackground()
{
_ = Task.Run(() =>
{
try
{
var currentSearchText = SearchText;
UpdateSearchText(currentSearchText, currentSearchText);
}
catch (Exception e)
{
Logger.LogError("Failed to reload search", e);
}
});
}
public override IListItem[] GetItems()
{
@@ -119,12 +149,23 @@ public partial class MainListPage : DynamicListPage,
_filteredItems = null;
}
// If the internal state has changed, reset _filteredItems to reset the list.
if (_filteredItemsIncludesApps != _includeApps)
{
_filteredItems = null;
}
// If we don't have any previous filter results to work with, start
// with a list of all our commands & apps.
if (_filteredItems == null)
{
IEnumerable<IListItem> apps = AllAppsCommandProvider.Page.GetItems();
_filteredItems = commands.Concat(apps);
_filteredItems = commands;
_filteredItemsIncludesApps = _includeApps;
if (_includeApps)
{
IEnumerable<IListItem> apps = AllAppsCommandProvider.Page.GetItems();
_filteredItems = _filteredItems.Concat(apps);
}
}
// Produce a list of everything that matches the current filter.

View File

@@ -13,6 +13,7 @@ public partial class QuitCommand : InvokableCommand, IFallbackHandler
{
public QuitCommand()
{
Id = "com.microsoft.cmdpal.quit";
Icon = new IconInfo("\uE711");
}

View File

@@ -0,0 +1,7 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace Microsoft.CmdPal.UI.ViewModels.Messages;
public record BeginInvokeMessage;

View File

@@ -0,0 +1,7 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace Microsoft.CmdPal.UI.ViewModels.Messages;
public record CmdPalInvokeResultMessage(Microsoft.CommandPalette.Extensions.CommandResultKind Kind);

View File

@@ -0,0 +1,10 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace Microsoft.CmdPal.UI.ViewModels.Messages;
public record GoBackMessage(bool WithAnimation = true, bool FocusSearch = true)
{
// TODO! sticking these properties here feels like leaking the UI into the models
}

View File

@@ -4,6 +4,7 @@
namespace Microsoft.CmdPal.UI.ViewModels.Messages;
public record GoHomeMessage()
// TODO! sticking these properties here feels like leaking the UI into the models
public record GoHomeMessage(bool WithAnimation = true, bool FocusSearch = true)
{
}

View File

@@ -0,0 +1,9 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace Microsoft.CmdPal.UI.ViewModels.Messages;
public record NavigateToPageMessage(PageViewModel Page, bool WithAnimation)
{
}

View File

@@ -0,0 +1,9 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace Microsoft.CmdPal.UI.ViewModels.Messages;
public record ShowConfirmationMessage(Microsoft.CommandPalette.Extensions.IConfirmationArgs? Args)
{
}

View File

@@ -0,0 +1,9 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace Microsoft.CmdPal.UI.ViewModels.Messages;
public record ShowToastMessage(string Message)
{
}

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

@@ -10,6 +10,8 @@ public class ProviderSettings
{
public bool IsEnabled { get; set; } = true;
public Dictionary<string, bool> FallbackCommands { get; set; } = [];
[JsonIgnore]
public string ProviderDisplayName { get; set; } = string.Empty;
@@ -42,4 +44,14 @@ public class ProviderSettings
throw new InvalidDataException("Did you add a built-in command and forget to set the Id? Make sure you do that!");
}
}
public bool IsFallbackEnabled(TopLevelViewModel command)
{
return FallbackCommands.TryGetValue(command.Id, out var enabled) ? enabled : true;
}
public void SetFallbackEnabled(TopLevelViewModel command, bool enabled)
{
FallbackCommands[command.Id] = enabled;
}
}

View File

@@ -25,7 +25,11 @@ public partial class ProviderSettingsViewModel(
public string ExtensionName => _provider.Extension?.ExtensionDisplayName ?? "Built-in";
public string ExtensionSubtext => IsEnabled ? $"{ExtensionName}, {TopLevelCommands.Count} commands" : Resources.builtin_disabled_extension;
public string ExtensionSubtext => IsEnabled ?
HasFallbackCommands ?
$"{ExtensionName}, {TopLevelCommands.Count} commands, {FallbackCommands.Count} fallback commands" :
$"{ExtensionName}, {TopLevelCommands.Count} commands" :
Resources.builtin_disabled_extension;
[MemberNotNullWhen(true, nameof(Extension))]
public bool IsFromExtension => _provider.Extension != null;
@@ -139,6 +143,31 @@ public partial class ProviderSettingsViewModel(
return [.. providersCommands];
}
[field: AllowNull]
public List<TopLevelViewModel> FallbackCommands
{
get
{
if (field == null)
{
field = BuildFallbackViewModels();
}
return field;
}
}
public bool HasFallbackCommands => _provider.FallbackItems?.Length > 0;
private List<TopLevelViewModel> BuildFallbackViewModels()
{
var thisProvider = _provider;
var providersCommands = thisProvider.FallbackItems;
// Remember! This comes in on the UI thread!
return [.. providersCommands];
}
private void Save() => SettingsModel.SaveSettings(_settings);
private void InitializeSettingsPage()

View File

@@ -12,63 +12,71 @@ public partial class RecentCommandsManager : ObservableObject
[JsonInclude]
internal List<HistoryItem> History { get; set; } = [];
private readonly Lock _lock = new();
public RecentCommandsManager()
{
}
public int GetCommandHistoryWeight(string commandId)
{
var entry = History
lock (_lock)
{
var entry = History
.Index()
.Where(item => item.Item.CommandId == commandId)
.FirstOrDefault();
// These numbers are vaguely scaled so that "VS" will make "Visual Studio" the
// match after one use.
// Usually it has a weight of 84, compared to 109 for the VS cmd prompt
if (entry.Item != null)
{
var index = entry.Index;
// First, add some weight based on how early in the list this appears
var bucket = index switch
// These numbers are vaguely scaled so that "VS" will make "Visual Studio" the
// match after one use.
// Usually it has a weight of 84, compared to 109 for the VS cmd prompt
if (entry.Item != null)
{
var i when index <= 2 => 35,
var i when index <= 10 => 25,
var i when index <= 15 => 15,
var i when index <= 35 => 10,
_ => 5,
};
var index = entry.Index;
// Then, add weight for how often this is used, but cap the weight from usage.
var uses = Math.Min(entry.Item.Uses * 5, 35);
// First, add some weight based on how early in the list this appears
var bucket = index switch
{
var i when index <= 2 => 35,
var i when index <= 10 => 25,
var i when index <= 15 => 15,
var i when index <= 35 => 10,
_ => 5,
};
return bucket + uses;
// Then, add weight for how often this is used, but cap the weight from usage.
var uses = Math.Min(entry.Item.Uses * 5, 35);
return bucket + uses;
}
return 0;
}
return 0;
}
public void AddHistoryItem(string commandId)
{
var entry = History
lock (_lock)
{
var entry = History
.Where(item => item.CommandId == commandId)
.FirstOrDefault();
if (entry == null)
{
var newitem = new HistoryItem() { CommandId = commandId, Uses = 1 };
History.Insert(0, newitem);
}
else
{
History.Remove(entry);
entry.Uses++;
History.Insert(0, entry);
}
if (entry == null)
{
var newitem = new HistoryItem() { CommandId = commandId, Uses = 1 };
History.Insert(0, newitem);
}
else
{
History.Remove(entry);
entry.Uses++;
History.Insert(0, entry);
}
if (History.Count > 50)
{
History.RemoveRange(50, History.Count - 50);
if (History.Count > 50)
{
History.RemoveRange(50, History.Count - 50);
}
}
}
}

View File

@@ -9,17 +9,22 @@ 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;
public partial class ShellViewModel(IServiceProvider _serviceProvider, TaskScheduler _scheduler) : ObservableObject
public partial class ShellViewModel : ObservableObject,
IRecipient<PerformCommandMessage>
{
private readonly IRootPageService _rootPageService;
private readonly TaskScheduler _scheduler;
private readonly IPageViewModelFactoryService _pageViewModelFactory;
private readonly Lock _invokeLock = new();
private Task? _handleInvokeTask;
[ObservableProperty]
public partial bool IsLoaded { get; set; } = false;
@@ -29,7 +34,7 @@ public partial class ShellViewModel(IServiceProvider _serviceProvider, TaskSched
[ObservableProperty]
public partial bool IsDetailsVisible { get; set; }
private PageViewModel _currentPage = new LoadingPageViewModel(null, _scheduler);
private PageViewModel _currentPage;
public PageViewModel CurrentPage
{
@@ -54,31 +59,48 @@ public partial class ShellViewModel(IServiceProvider _serviceProvider, TaskSched
}
}
private MainListPage? _mainListPage;
private IPage? _rootPage;
private IExtensionWrapper? _activeExtension;
private bool _isNested;
public bool IsNested { get => _isNested; }
public ShellViewModel(TaskScheduler scheduler, IRootPageService rootPageService, IPageViewModelFactoryService pageViewModelFactory)
{
_pageViewModelFactory = pageViewModelFactory;
_scheduler = scheduler;
_rootPageService = rootPageService;
_currentPage = new LoadingPageViewModel(null, _scheduler);
// Register to receive messages
WeakReferenceMessenger.Default.Register<PerformCommandMessage>(this);
}
[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;
@@ -153,14 +175,227 @@ public partial class ShellViewModel(IServiceProvider _serviceProvider, TaskSched
public void PerformTopLevelCommand(PerformCommandMessage message)
{
if (_mainListPage == null)
_rootPageService.OnPerformTopLevelCommand(message.Context);
}
public void Receive(PerformCommandMessage message)
{
PerformCommand(message);
}
private void PerformCommand(PerformCommandMessage message)
{
var command = message.Command.Unsafe;
if (command == null)
{
return;
}
if (message.Context is IListItem listItem)
if (!CurrentPage.IsNested)
{
_mainListPage.UpdateHistory(listItem);
// on the main page here
PerformTopLevelCommand(message);
}
IExtensionWrapper? extension = null;
try
{
// In the case that we're coming from a top-level command, the
// current page's host is the global instance. We only really want
// to use that as the host of last resort.
var pageHost = CurrentPage?.ExtensionHost;
if (pageHost == CommandPaletteHost.Instance)
{
pageHost = null;
}
var messageHost = message.ExtensionHost;
// Use the host from the current page if it has one, else use the
// one specified in the PerformMessage for a top-level command,
// else just use the global one.
CommandPaletteHost host;
// Top level items can come through without a Extension set on the
// message. In that case, the `Context` is actually the
// TopLevelViewModel itself, and we can use that to get at the
// extension object.
extension = pageHost?.Extension ?? messageHost?.Extension ?? null;
if (extension == null && message.Context is TopLevelViewModel topLevelViewModel)
{
extension = topLevelViewModel.ExtensionHost?.Extension;
host = pageHost ?? messageHost ?? topLevelViewModel?.ExtensionHost ?? CommandPaletteHost.Instance;
}
else
{
host = pageHost ?? messageHost ?? CommandPaletteHost.Instance;
}
if (extension != null)
{
if (messageHost != null)
{
Logger.LogDebug($"Activated top-level command from {extension.ExtensionDisplayName}");
}
else
{
Logger.LogDebug($"Activated command from {extension.ExtensionDisplayName}");
}
}
SetActiveExtension(extension);
if (command is IPage page)
{
Logger.LogDebug($"Navigating to page");
var isMainPage = command == _rootPage;
_isNested = !isMainPage;
// Construct our ViewModel of the appropriate type and pass it the UI Thread context.
var pageViewModel = _pageViewModelFactory.TryCreatePageViewModel(page, _isNested, host);
if (pageViewModel == null)
{
Logger.LogError($"Failed to create ViewModel for page {page.GetType().Name}");
throw new NotSupportedException();
}
// Kick off async loading of our ViewModel
LoadPageViewModel(pageViewModel);
OnUIThread(() => { WeakReferenceMessenger.Default.Send<UpdateCommandBarMessage>(new(null)); });
WeakReferenceMessenger.Default.Send<NavigateToPageMessage>(new(pageViewModel, message.WithAnimation));
// Note: Originally we set our page back in the ViewModel here, but that now happens in response to the Frame navigating triggered from the above
// See RootFrame_Navigated event handler.
}
else if (command is IInvokableCommand invokable)
{
Logger.LogDebug($"Invoking command");
WeakReferenceMessenger.Default.Send<BeginInvokeMessage>();
StartInvoke(message, invokable);
}
}
catch (Exception ex)
{
// TODO: It would be better to do this as a page exception, rather
// than a silent log message.
CommandPaletteHost.Instance.Log(ex.Message);
}
}
private void StartInvoke(PerformCommandMessage message, IInvokableCommand invokable)
{
// TODO GH #525 This needs more better locking.
lock (_invokeLock)
{
if (_handleInvokeTask != null)
{
// do nothing - a command is already doing a thing
}
else
{
_handleInvokeTask = Task.Run(() =>
{
SafeHandleInvokeCommandSynchronous(message, invokable);
});
}
}
}
private void SafeHandleInvokeCommandSynchronous(PerformCommandMessage message, IInvokableCommand invokable)
{
try
{
// Call out to extension process.
// * May fail!
// * May never return!
var result = invokable.Invoke(message.Context);
// But if it did succeed, we need to handle the result.
UnsafeHandleCommandResult(result);
_handleInvokeTask = null;
}
catch (Exception ex)
{
_handleInvokeTask = null;
// TODO: It would be better to do this as a page exception, rather
// than a silent log message.
CommandPaletteHost.Instance.Log(ex.Message);
}
}
private void UnsafeHandleCommandResult(ICommandResult? result)
{
if (result == null)
{
// No result, nothing to do.
return;
}
var kind = result.Kind;
Logger.LogDebug($"handling {kind.ToString()}");
WeakReferenceMessenger.Default.Send<CmdPalInvokeResultMessage>(new(kind));
switch (kind)
{
case CommandResultKind.Dismiss:
{
// Reset the palette to the main page and dismiss
GoHome(withAnimation: false, focusSearch: false);
WeakReferenceMessenger.Default.Send<DismissMessage>();
break;
}
case CommandResultKind.GoHome:
{
// Go back to the main page, but keep it open
GoHome();
break;
}
case CommandResultKind.GoBack:
{
GoBack();
break;
}
case CommandResultKind.Hide:
{
// Keep this page open, but hide the palette.
WeakReferenceMessenger.Default.Send<DismissMessage>();
break;
}
case CommandResultKind.KeepOpen:
{
// Do nothing.
break;
}
case CommandResultKind.Confirm:
{
if (result.Args is IConfirmationArgs a)
{
WeakReferenceMessenger.Default.Send<ShowConfirmationMessage>(new(a));
}
break;
}
case CommandResultKind.ShowToast:
{
if (result.Args is IToastArgs a)
{
WeakReferenceMessenger.Default.Send<ShowToastMessage>(new(a.Message));
UnsafeHandleCommandResult(a.Result);
}
break;
}
}
}
@@ -196,9 +431,15 @@ public partial class ShellViewModel(IServiceProvider _serviceProvider, TaskSched
}
}
public void GoHome()
public void GoHome(bool withAnimation = true, bool focusSearch = true)
{
SetActiveExtension(null);
WeakReferenceMessenger.Default.Send<GoHomeMessage>(new(withAnimation, focusSearch));
}
public void GoBack(bool withAnimation = true, bool focusSearch = true)
{
WeakReferenceMessenger.Default.Send<GoBackMessage>(new(withAnimation, focusSearch));
}
// You may ask yourself, why aren't we using CsWin32 for this?
@@ -214,4 +455,13 @@ public partial class ShellViewModel(IServiceProvider _serviceProvider, TaskSched
[SupportedOSPlatform("windows5.0")]
internal static extern unsafe global::Windows.Win32.Foundation.HRESULT CoAllowSetForegroundWindow(nint pUnk, [Optional] void* lpvReserved);
}
private void OnUIThread(Action action)
{
_ = Task.Factory.StartNew(
action,
CancellationToken.None,
TaskCreationOptions.None,
_scheduler);
}
}

View File

@@ -26,6 +26,7 @@ public partial class TopLevelCommandManager : ObservableObject,
private readonly List<CommandProviderWrapper> _builtInCommands = [];
private readonly List<CommandProviderWrapper> _extensionCommandProviders = [];
private readonly Lock _commandProvidersLock = new();
TaskScheduler IPageContext.Scheduler => _taskScheduler;
@@ -41,14 +42,26 @@ public partial class TopLevelCommandManager : ObservableObject,
[ObservableProperty]
public partial bool IsLoading { get; private set; } = true;
public IEnumerable<CommandProviderWrapper> CommandProviders => _builtInCommands.Concat(_extensionCommandProviders);
public IEnumerable<CommandProviderWrapper> CommandProviders
{
get
{
lock (_commandProvidersLock)
{
return _builtInCommands.Concat(_extensionCommandProviders).ToList();
}
}
}
public async Task<bool> LoadBuiltinsAsync()
{
var s = new Stopwatch();
s.Start();
_builtInCommands.Clear();
lock (_commandProvidersLock)
{
_builtInCommands.Clear();
}
// Load built-In commands first. These are all in-proc, and
// owned by our ServiceProvider.
@@ -56,7 +69,11 @@ public partial class TopLevelCommandManager : ObservableObject,
foreach (var provider in builtInCommands)
{
CommandProviderWrapper wrapper = new(provider, _taskScheduler);
_builtInCommands.Add(wrapper);
lock (_commandProvidersLock)
{
_builtInCommands.Add(wrapper);
}
var commands = await LoadTopLevelCommandsFromProvider(wrapper);
lock (TopLevelCommands)
{
@@ -81,19 +98,28 @@ public partial class TopLevelCommandManager : ObservableObject,
await commandProvider.LoadTopLevelCommands(_serviceProvider, weakSelf);
var settings = _serviceProvider.GetService<SettingsModel>()!;
var commands = await Task.Factory.StartNew(
() =>
{
List<TopLevelViewModel> commands = [];
foreach (var item in commandProvider.TopLevelItems)
{
commands.Add(item);
}
List<TopLevelViewModel> commands = [];
foreach (var item in commandProvider.FallbackItems)
{
if (item.IsEnabled)
{
commands.Add(item);
}
}
foreach (var item in commandProvider.TopLevelItems)
{
commands.Add(item);
}
foreach (var item in commandProvider.FallbackItems)
{
commands.Add(item);
}
return commands;
},
CancellationToken.None,
TaskCreationOptions.None,
_taskScheduler);
commandProvider.CommandsChanged -= CommandProvider_CommandsChanged;
commandProvider.CommandsChanged += CommandProvider_CommandsChanged;
@@ -159,7 +185,10 @@ public partial class TopLevelCommandManager : ObservableObject,
foreach (var i in sender.FallbackItems)
{
newItems.Add(i);
if (i.IsEnabled)
{
newItems.Add(i);
}
}
// Slice out the old commands
@@ -185,6 +214,7 @@ public partial class TopLevelCommandManager : ObservableObject,
IsLoading = true;
var extensionService = _serviceProvider.GetService<IExtensionService>()!;
await extensionService.SignalStopExtensionsAsync();
lock (TopLevelCommands)
{
TopLevelCommands.Clear();
@@ -210,7 +240,11 @@ public partial class TopLevelCommandManager : ObservableObject,
extensionService.OnExtensionRemoved -= ExtensionService_OnExtensionRemoved;
var extensions = (await extensionService.GetInstalledExtensionsAsync()).ToImmutableList();
_extensionCommandProviders.Clear();
lock (_commandProvidersLock)
{
_extensionCommandProviders.Clear();
}
if (extensions != null)
{
await StartExtensionsAndGetCommands(extensions);
@@ -247,9 +281,9 @@ public partial class TopLevelCommandManager : ObservableObject,
// Wait for all extensions to start
var wrappers = (await Task.WhenAll(startTasks)).Where(wrapper => wrapper != null).Select(w => w!).ToList();
foreach (var wrapper in wrappers)
lock (_commandProvidersLock)
{
_extensionCommandProviders.Add(wrapper!);
_extensionCommandProviders.AddRange(wrappers);
}
// Load the commands from the providers in parallel
@@ -375,4 +409,13 @@ public partial class TopLevelCommandManager : ObservableObject,
var errorMessage = $"A bug occurred in {$"the \"{extensionHint}\"" ?? "an unknown's"} extension's code:\n{ex.Message}\n{ex.Source}\n{ex.StackTrace}\n\n";
CommandPaletteHost.Instance.Log(errorMessage);
}
internal bool IsProviderActive(string id)
{
lock (_commandProvidersLock)
{
return _builtInCommands.Any(wrapper => wrapper.Id == id && wrapper.IsActive)
|| _extensionCommandProviders.Any(wrapper => wrapper.Id == id && wrapper.IsActive);
}
}
}

View File

@@ -4,7 +4,9 @@
using System.Collections.ObjectModel;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Messaging;
using ManagedCommon;
using Microsoft.CmdPal.UI.ViewModels.Messages;
using Microsoft.CmdPal.UI.ViewModels.Settings;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
@@ -17,6 +19,7 @@ namespace Microsoft.CmdPal.UI.ViewModels;
public sealed partial class TopLevelViewModel : ObservableObject, IListItem
{
private readonly SettingsModel _settings;
private readonly ProviderSettings _providerSettings;
private readonly IServiceProvider _serviceProvider;
private readonly CommandItemViewModel _commandItemViewModel;
@@ -77,6 +80,9 @@ public sealed partial class TopLevelViewModel : ObservableObject, IListItem
////// INotifyPropChanged
public event TypedEventHandler<object, IPropChangedEventArgs>? PropChanged;
// Fallback items
public string DisplayTitle { get; private set; } = string.Empty;
public HotkeySettings? Hotkey
{
get => _hotkey;
@@ -133,16 +139,32 @@ public sealed partial class TopLevelViewModel : ObservableObject, IListItem
}
}
public bool IsEnabled
{
get => _providerSettings.IsFallbackEnabled(this);
set
{
if (value != IsEnabled)
{
_providerSettings.SetFallbackEnabled(this, value);
Save();
WeakReferenceMessenger.Default.Send<ReloadCommandsMessage>(new());
}
}
}
public TopLevelViewModel(
CommandItemViewModel item,
bool isFallback,
CommandPaletteHost extensionHost,
string commandProviderId,
SettingsModel settings,
ProviderSettings providerSettings,
IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
_settings = settings;
_providerSettings = providerSettings;
_commandProviderId = commandProviderId;
_commandItemViewModel = item;
@@ -156,6 +178,22 @@ public sealed partial class TopLevelViewModel : ObservableObject, IListItem
// UpdateTags();
}
internal void InitializeProperties()
{
ItemViewModel.SlowInitializeProperties();
if (IsFallback)
{
var model = _commandItemViewModel.Model.Unsafe;
// RPC to check type
if (model is IFallbackCommandItem fallback)
{
DisplayTitle = fallback.DisplayTitle;
}
}
}
private void Item_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (!string.IsNullOrEmpty(e.PropertyName))
@@ -240,7 +278,7 @@ public sealed partial class TopLevelViewModel : ObservableObject, IListItem
{
// Use WyHash64 to generate stable ID hashes.
// manually seeding with 0, so that the hash is stable across launches
var result = WyHash64.ComputeHash64(_commandProviderId + Title + Subtitle, seed: 0);
var result = WyHash64.ComputeHash64(_commandProviderId + DisplayTitle + Title + Subtitle, seed: 0);
_generatedId = $"{_commandProviderId}{result}";
}
@@ -263,6 +301,11 @@ public sealed partial class TopLevelViewModel : ObservableObject, IListItem
return false;
}
if (!IsEnabled)
{
return false;
}
try
{
return UnsafeUpdateFallbackSynchronous(newQuery);

View File

@@ -8,6 +8,7 @@ using Microsoft.CmdPal.Common.Services;
using Microsoft.CmdPal.Ext.Apps;
using Microsoft.CmdPal.Ext.Bookmarks;
using Microsoft.CmdPal.Ext.Calc;
using Microsoft.CmdPal.Ext.ClipboardHistory;
using Microsoft.CmdPal.Ext.Indexer;
using Microsoft.CmdPal.Ext.Registry;
using Microsoft.CmdPal.Ext.Shell;
@@ -104,6 +105,7 @@ public partial class App : Application
services.AddSingleton<ICommandProvider, WindowWalkerCommandsProvider>();
services.AddSingleton<ICommandProvider, WebSearchCommandsProvider>();
services.AddSingleton<ICommandProvider, ClipboardHistoryCommandsProvider>();
// GH #38440: Users might not have WinGet installed! Or they might have
// a ridiculously old version. Or might be running as admin.
@@ -142,8 +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

@@ -0,0 +1,40 @@
// 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 CommunityToolkit.Mvvm.Messaging;
using Microsoft.CmdPal.UI.Events;
using Microsoft.CmdPal.UI.ViewModels.Messages;
using Microsoft.PowerToys.Telemetry;
namespace Microsoft.CmdPal.UI;
/// <summary>
/// TelemetryForwarder is responsible for forwarding telemetry events from the
/// command palette core to PowerToys Telemetry.
/// This allows us to emit telemetry events as messages from the core,
/// and then handle them by logging to our PT telemetry provider.
///
/// We may in the future want to replace this with a more generic "ITelemetryService"
/// or something similar, but this works for now.
/// </summary>
internal sealed class TelemetryForwarder :
IRecipient<BeginInvokeMessage>,
IRecipient<CmdPalInvokeResultMessage>
{
public TelemetryForwarder()
{
WeakReferenceMessenger.Default.Register<BeginInvokeMessage>(this);
WeakReferenceMessenger.Default.Register<CmdPalInvokeResultMessage>(this);
}
public void Receive(CmdPalInvokeResultMessage message)
{
PowerToysTelemetry.Log.WriteEvent(new CmdPalInvokeResult(message.Kind));
}
public void Receive(BeginInvokeMessage message)
{
PowerToysTelemetry.Log.WriteEvent(new BeginInvoke());
}
}

View File

@@ -6,6 +6,7 @@ using System.Diagnostics;
using System.Runtime.InteropServices;
using CmdPalKeyboardService;
using CommunityToolkit.Mvvm.Messaging;
using ManagedCommon;
using Microsoft.CmdPal.Common.Helpers;
using Microsoft.CmdPal.Common.Messages;
using Microsoft.CmdPal.Common.Services;
@@ -106,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", () =>
@@ -174,6 +175,8 @@ public sealed partial class MainWindow : WindowEx,
private void UpdateAcrylic()
{
_acrylicController?.RemoveAllSystemBackdropTargets();
_acrylicController = GetAcrylicConfig(Content);
// Enable the system backdrop.
@@ -233,6 +236,10 @@ public sealed partial class MainWindow : WindowEx,
PInvoke.SetForegroundWindow(hwnd);
PInvoke.SetActiveWindow(hwnd);
// Push our window to the top of the Z-order and make it the topmost, so that it appears above all other windows.
// We want to remove the topmost status when we hide the window (because we cloak it instead of hiding it).
PInvoke.SetWindowPos(hwnd, HWND.HWND_TOPMOST, 0, 0, 0, 0, SET_WINDOW_POS_FLAGS.SWP_NOMOVE | SET_WINDOW_POS_FLAGS.SWP_NOSIZE);
}
private DisplayArea GetScreen(HWND currentHwnd, MonitorBehavior target)
@@ -302,8 +309,14 @@ public sealed partial class MainWindow : WindowEx,
// This might come in on a background thread
DispatcherQueue.TryEnqueue(() => Close());
public void Receive(DismissMessage message) =>
HideWindow();
public void Receive(DismissMessage message)
{
// This might come in off the UI thread. Make sure to hop back.
DispatcherQueue.TryEnqueue(() =>
{
HideWindow();
});
}
private void HideWindow()
{
@@ -331,6 +344,10 @@ public sealed partial class MainWindow : WindowEx,
BOOL value = true;
PInvoke.DwmSetWindowAttribute(_hwnd, DWMWINDOWATTRIBUTE.DWMWA_CLOAK, &value, (uint)sizeof(BOOL));
}
// Because we're only cloaking the window, bury it at the bottom in case something can
// see it - e.g. some accessibility helper (note: this also removes the top-most status).
PInvoke.SetWindowPos(_hwnd, HWND.HWND_BOTTOM, 0, 0, 0, 0, SET_WINDOW_POS_FLAGS.SWP_NOMOVE | SET_WINDOW_POS_FLAGS.SWP_NOSIZE);
}
private void Uncloak()
@@ -450,30 +467,42 @@ public sealed partial class MainWindow : WindowEx,
return;
}
if (activatedEventArgs.Kind == Microsoft.Windows.AppLifecycle.ExtendedActivationKind.Protocol)
try
{
if (activatedEventArgs.Data is IProtocolActivatedEventArgs protocolArgs)
if (activatedEventArgs.Kind == ExtendedActivationKind.StartupTask)
{
if (protocolArgs.Uri.ToString() is string uri)
{
// was the URI "x-cmdpal://background" ?
if (uri.StartsWith("x-cmdpal://background", StringComparison.OrdinalIgnoreCase))
{
// we're running, we don't want to activate our window. bail
return;
}
else if (uri.StartsWith("x-cmdpal://settings", StringComparison.OrdinalIgnoreCase))
{
WeakReferenceMessenger.Default.Send<OpenSettingsMessage>(new());
return;
}
}
return;
}
if (activatedEventArgs.Kind == ExtendedActivationKind.Protocol)
{
if (activatedEventArgs.Data is IProtocolActivatedEventArgs protocolArgs)
{
if (protocolArgs.Uri.ToString() is string uri)
{
// was the URI "x-cmdpal://background" ?
if (uri.StartsWith("x-cmdpal://background", StringComparison.OrdinalIgnoreCase))
{
// we're running, we don't want to activate our window. bail
return;
}
else if (uri.StartsWith("x-cmdpal://settings", StringComparison.OrdinalIgnoreCase))
{
WeakReferenceMessenger.Default.Send<OpenSettingsMessage>(new());
return;
}
}
}
}
}
catch (COMException ex)
{
// Accessing properties activatedEventArgs.Kind and activatedEventArgs.Data might cause COMException
// if the args are not valid or not passed correctly.
Logger.LogError("COM exception when activating the application", ex);
}
Activate();
Summon(string.Empty);
}
public void Summon(string commandId) =>

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" />
@@ -107,6 +115,7 @@
<ItemGroup>
<ProjectReference Include="..\..\..\common\ManagedTelemetry\Telemetry\ManagedTelemetry.csproj" />
<ProjectReference Include="..\ext\Microsoft.CmdPal.Ext.ClipboardHistory\Microsoft.CmdPal.Ext.ClipboardHistory.csproj" />
<ProjectReference Include="..\ext\Microsoft.CmdPal.Ext.System\Microsoft.CmdPal.Ext.System.csproj" />
<ProjectReference Include="..\ext\Microsoft.CmdPal.Ext.WebSearch\Microsoft.CmdPal.Ext.WebSearch.csproj" />
<ProjectReference Include="..\ext\Microsoft.CmdPal.Ext.Indexer\Microsoft.CmdPal.Ext.Indexer.csproj" />

View File

@@ -6,6 +6,8 @@ SetForegroundWindow
GetWindowRect
GetCursorPos
SetWindowPos
HWND_TOPMOST
HWND_BOTTOM
IsIconic
RegisterHotKey
UnregisterHotKey
@@ -20,7 +22,6 @@ SetActiveWindow
MonitorFromWindow
GetMonitorInfo
GetDpiForMonitor
CoAllowSetForegroundWindow
WM_HOTKEY
WM_NCLBUTTONDBLCLK
@@ -43,3 +44,7 @@ MessageBox
DwmGetWindowAttribute
DwmSetWindowAttribute
DWM_CLOAKED_APP
CoWaitForMultipleObjects
INFINITE
CWMO_FLAGS

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

@@ -6,20 +6,21 @@ using System.ComponentModel;
using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.WinUI;
using ManagedCommon;
using Microsoft.CmdPal.Common.Services;
using Microsoft.CmdPal.UI.Events;
using Microsoft.CmdPal.UI.Settings;
using Microsoft.CmdPal.UI.ViewModels;
using Microsoft.CmdPal.UI.ViewModels.MainPage;
using Microsoft.CmdPal.UI.ViewModels.Messages;
using Microsoft.CommandPalette.Extensions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.PowerToys.Telemetry;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Input;
using Microsoft.UI.Xaml.Controls;
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;
@@ -28,15 +29,18 @@ namespace Microsoft.CmdPal.UI.Pages;
/// </summary>
public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
IRecipient<NavigateBackMessage>,
IRecipient<PerformCommandMessage>,
IRecipient<OpenSettingsMessage>,
IRecipient<HotkeySummonMessage>,
IRecipient<ShowDetailsMessage>,
IRecipient<HideDetailsMessage>,
IRecipient<ClearSearchMessage>,
IRecipient<HandleCommandResultMessage>,
IRecipient<LaunchUriMessage>,
IRecipient<SettingsWindowClosedMessage>,
IRecipient<GoHomeMessage>,
IRecipient<GoBackMessage>,
IRecipient<ShowConfirmationMessage>,
IRecipient<ShowToastMessage>,
IRecipient<NavigateToPageMessage>,
INotifyPropertyChanged
{
private readonly DispatcherQueue _queue = DispatcherQueue.GetForCurrentThread();
@@ -50,8 +54,6 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
private readonly ToastWindow _toast = new();
private readonly Lock _invokeLock = new();
private Task? _handleInvokeTask;
private SettingsWindow? _settingsWindow;
public ShellViewModel ViewModel { get; private set; } = App.Current.Services.GetService<ShellViewModel>()!;
@@ -64,8 +66,6 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
// how we are doing navigation around
WeakReferenceMessenger.Default.Register<NavigateBackMessage>(this);
WeakReferenceMessenger.Default.Register<PerformCommandMessage>(this);
WeakReferenceMessenger.Default.Register<HandleCommandResultMessage>(this);
WeakReferenceMessenger.Default.Register<OpenSettingsMessage>(this);
WeakReferenceMessenger.Default.Register<HotkeySummonMessage>(this);
WeakReferenceMessenger.Default.Register<SettingsWindowClosedMessage>(this);
@@ -76,6 +76,15 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
WeakReferenceMessenger.Default.Register<ClearSearchMessage>(this);
WeakReferenceMessenger.Default.Register<LaunchUriMessage>(this);
WeakReferenceMessenger.Default.Register<GoHomeMessage>(this);
WeakReferenceMessenger.Default.Register<GoBackMessage>(this);
WeakReferenceMessenger.Default.Register<ShowConfirmationMessage>(this);
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);
}
@@ -103,195 +112,72 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
}
}
public void Receive(PerformCommandMessage message)
public void Receive(NavigateToPageMessage message)
{
PerformCommand(message);
// TODO GH #526 This needs more better locking too
_ = _queue.TryEnqueue(() =>
{
// Also hide our details pane about here, if we had one
HideDetails();
// Navigate to the appropriate host page for that VM
RootFrame.Navigate(
message.Page switch
{
ListViewModel => typeof(ListPage),
ContentPageViewModel => typeof(ContentPage),
_ => throw new NotSupportedException(),
},
message.Page,
message.WithAnimation ? _slideRightTransition : _noAnimation);
PowerToysTelemetry.Log.WriteEvent(new OpenPage(RootFrame.BackStackDepth));
// Refocus on the Search for continual typing on the next search request
SearchBox.Focus(Microsoft.UI.Xaml.FocusState.Programmatic);
if (!ViewModel.IsNested)
{
// todo BODGY
RootFrame.BackStack.Clear();
}
});
}
private void PerformCommand(PerformCommandMessage message)
public void Receive(ShowConfirmationMessage message)
{
var command = message.Command.Unsafe;
if (command == null)
DispatcherQueue.TryEnqueue(async () =>
{
try
{
await HandleConfirmArgsOnUiThread(message.Args);
}
catch (Exception ex)
{
Logger.LogError(ex.ToString());
}
});
}
public void Receive(ShowToastMessage message)
{
DispatcherQueue.TryEnqueue(() =>
{
_toast.ShowToast(message.Message);
});
}
// This gets called from the UI thread
private async Task HandleConfirmArgsOnUiThread(IConfirmationArgs? args)
{
if (args == null)
{
return;
}
if (!ViewModel.CurrentPage.IsNested)
{
// on the main page here
ViewModel.PerformTopLevelCommand(message);
}
IExtensionWrapper? extension = null;
// TODO: Actually loading up the page, or invoking the command -
// that might belong in the model, not the view?
// Especially considering the try/catch concerns around the fact that the
// COM call might just fail.
// Or the command may be a stub. Future us problem.
try
{
// In the case that we're coming from a top-level command, the
// current page's host is the global instance. We only really want
// to use that as the host of last resort.
var pageHost = ViewModel.CurrentPage?.ExtensionHost;
if (pageHost == CommandPaletteHost.Instance)
{
pageHost = null;
}
var messageHost = message.ExtensionHost;
// Use the host from the current page if it has one, else use the
// one specified in the PerformMessage for a top-level command,
// else just use the global one.
CommandPaletteHost host;
// Top level items can come through without a Extension set on the
// message. In that case, the `Context` is actually the
// TopLevelViewModel itself, and we can use that to get at the
// extension object.
extension = pageHost?.Extension ?? messageHost?.Extension ?? null;
if (extension == null && message.Context is TopLevelViewModel topLevelViewModel)
{
extension = topLevelViewModel.ExtensionHost?.Extension;
host = pageHost ?? messageHost ?? topLevelViewModel?.ExtensionHost ?? CommandPaletteHost.Instance;
}
else
{
host = pageHost ?? messageHost ?? CommandPaletteHost.Instance;
}
if (extension != null)
{
if (messageHost != null)
{
Logger.LogDebug($"Activated top-level command from {extension.ExtensionDisplayName}");
}
else
{
Logger.LogDebug($"Activated command from {extension.ExtensionDisplayName}");
}
}
ViewModel.SetActiveExtension(extension);
if (command is IPage page)
{
Logger.LogDebug($"Navigating to page");
// TODO GH #526 This needs more better locking too
_ = _queue.TryEnqueue(() =>
{
// Also hide our details pane about here, if we had one
HideDetails();
WeakReferenceMessenger.Default.Send<UpdateCommandBarMessage>(new(null));
var isMainPage = command is MainListPage;
// Construct our ViewModel of the appropriate type and pass it the UI Thread context.
PageViewModel pageViewModel = page switch
{
IListPage listPage => new ListViewModel(listPage, _mainTaskScheduler, host)
{
IsNested = !isMainPage,
},
IContentPage contentPage => new ContentPageViewModel(contentPage, _mainTaskScheduler, host),
_ => throw new NotSupportedException(),
};
// Kick off async loading of our ViewModel
ViewModel.LoadPageViewModel(pageViewModel);
// Navigate to the appropriate host page for that VM
RootFrame.Navigate(
page switch
{
IListPage => typeof(ListPage),
IContentPage => typeof(ContentPage),
_ => throw new NotSupportedException(),
},
pageViewModel,
message.WithAnimation ? _slideRightTransition : _noAnimation);
PowerToysTelemetry.Log.WriteEvent(new OpenPage(RootFrame.BackStackDepth));
// Refocus on the Search for continual typing on the next search request
SearchBox.Focus(Microsoft.UI.Xaml.FocusState.Programmatic);
if (isMainPage)
{
// todo BODGY
RootFrame.BackStack.Clear();
}
// Note: Originally we set our page back in the ViewModel here, but that now happens in response to the Frame navigating triggered from the above
// See RootFrame_Navigated event handler.
});
}
else if (command is IInvokableCommand invokable)
{
Logger.LogDebug($"Invoking command");
PowerToysTelemetry.Log.WriteEvent(new BeginInvoke());
HandleInvokeCommand(message, invokable);
}
}
catch (Exception ex)
{
// TODO: It would be better to do this as a page exception, rather
// than a silent log message.
CommandPaletteHost.Instance.Log(ex.Message);
}
}
private void HandleInvokeCommand(PerformCommandMessage message, IInvokableCommand invokable)
{
// TODO GH #525 This needs more better locking.
lock (_invokeLock)
{
if (_handleInvokeTask != null)
{
// do nothing - a command is already doing a thing
}
else
{
_handleInvokeTask = Task.Run(() =>
{
try
{
var result = invokable.Invoke(message.Context);
DispatcherQueue.TryEnqueue(() =>
{
try
{
HandleCommandResultOnUiThread(result);
}
finally
{
_handleInvokeTask = null;
}
});
}
catch (Exception ex)
{
_handleInvokeTask = null;
// TODO: It would be better to do this as a page exception, rather
// than a silent log message.
CommandPaletteHost.Instance.Log(ex.Message);
}
});
}
}
}
// This gets called from the UI thread
private void HandleConfirmArgs(IConfirmationArgs args)
{
ConfirmResultViewModel vm = new(args, new(ViewModel.CurrentPage));
var initializeDialogTask = Task.Run(() => { InitializeConfirmationDialog(vm); });
initializeDialogTask.Wait();
await initializeDialogTask;
var resourceLoader = Microsoft.CmdPal.UI.Helpers.ResourceLoaderInstance.ResourceLoader;
var confirmText = resourceLoader.GetString("ConfirmationDialog_ConfirmButtonText");
@@ -322,19 +208,16 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
// };
}
DispatcherQueue.TryEnqueue(async () =>
var result = await dialog.ShowAsync();
if (result == ContentDialogResult.Primary)
{
var result = await dialog.ShowAsync();
if (result == ContentDialogResult.Primary)
{
var performMessage = new PerformCommandMessage(vm);
PerformCommand(performMessage);
}
else
{
// cancel
}
});
var performMessage = new PerformCommandMessage(vm);
WeakReferenceMessenger.Default.Send(performMessage);
}
else
{
// cancel
}
}
private void InitializeConfirmationDialog(ConfirmResultViewModel vm)
@@ -342,79 +225,6 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
vm.SafeInitializePropertiesSynchronous();
}
private void HandleCommandResultOnUiThread(ICommandResult? result)
{
try
{
if (result != null)
{
var kind = result.Kind;
Logger.LogDebug($"handling {kind.ToString()}");
PowerToysTelemetry.Log.WriteEvent(new CmdPalInvokeResult(kind));
switch (kind)
{
case CommandResultKind.Dismiss:
{
// Reset the palette to the main page and dismiss
GoHome(withAnimation: false, focusSearch: false);
WeakReferenceMessenger.Default.Send<DismissMessage>();
break;
}
case CommandResultKind.GoHome:
{
// Go back to the main page, but keep it open
GoHome();
break;
}
case CommandResultKind.GoBack:
{
GoBack();
break;
}
case CommandResultKind.Hide:
{
// Keep this page open, but hide the palette.
WeakReferenceMessenger.Default.Send<DismissMessage>();
break;
}
case CommandResultKind.KeepOpen:
{
// Do nothing.
break;
}
case CommandResultKind.Confirm:
{
if (result.Args is IConfirmationArgs a)
{
HandleConfirmArgs(a);
}
break;
}
case CommandResultKind.ShowToast:
{
if (result.Args is IToastArgs a)
{
_toast.ShowToast(a.Message);
HandleCommandResultOnUiThread(a.Result);
}
break;
}
}
}
}
catch
{
}
}
public void Receive(OpenSettingsMessage message)
{
_ = DispatcherQueue.TryEnqueue(() =>
@@ -467,14 +277,6 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
public void Receive(LaunchUriMessage message) => _ = global::Windows.System.Launcher.LaunchUriAsync(message.Uri);
public void Receive(HandleCommandResultMessage message)
{
DispatcherQueue.TryEnqueue(() =>
{
HandleCommandResultOnUiThread(message.Result.Unsafe);
});
}
private void HideDetails()
{
ViewModel.Details = null;
@@ -554,6 +356,11 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
WeakReferenceMessenger.Default.Send<FocusSearchBoxMessage>();
}
public void Receive(GoBackMessage message)
{
_ = DispatcherQueue.TryEnqueue(() => GoBack(message.WithAnimation, message.FocusSearch));
}
private void GoBack(bool withAnimation = true, bool focusSearch = true)
{
HideDetails();
@@ -591,14 +398,17 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
}
}
public void Receive(GoHomeMessage message)
{
_ = DispatcherQueue.TryEnqueue(() => GoHome(withAnimation: message.WithAnimation, focusSearch: message.FocusSearch));
}
private void GoHome(bool withAnimation = true, bool focusSearch = true)
{
while (RootFrame.CanGoBack)
{
GoBack(withAnimation, focusSearch);
}
WeakReferenceMessenger.Default.Send<GoHomeMessage>();
}
private void BackButton_Tapped(object sender, Microsoft.UI.Xaml.Input.TappedRoutedEventArgs e) => WeakReferenceMessenger.Default.Send<NavigateBackMessage>(new());
@@ -638,4 +448,32 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
WeakReferenceMessenger.Default.Send<PerformCommandMessage>(new(commandViewModel.Model));
}
}
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
{
var ptr = e.Pointer;
if (ptr.PointerDeviceType == PointerDeviceType.Mouse)
{
var ptrPt = e.GetCurrentPoint(this);
if (ptrPt.Properties.IsXButton1Pressed)
{
WeakReferenceMessenger.Default.Send(new NavigateBackMessage());
}
}
}
catch (Exception ex)
{
Logger.LogError("Error handling mouse button press event", ex);
}
}
}

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

@@ -6,9 +6,11 @@ using System.Runtime.InteropServices;
using ManagedCommon;
using Microsoft.CmdPal.UI.Events;
using Microsoft.PowerToys.Telemetry;
using Microsoft.UI.Dispatching;
using Microsoft.Windows.AppLifecycle;
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.System.Com;
using Windows.Win32.UI.WindowsAndMessaging;
namespace Microsoft.CmdPal.UI;
@@ -18,6 +20,7 @@ namespace Microsoft.CmdPal.UI;
// https://github.com/microsoft/WindowsAppSDK-Samples/tree/main/Samples/AppLifecycle/Instancing/cs2/cs-winui-packaged/CsWinUiDesktopInstancing
internal sealed class Program
{
private static DispatcherQueueSynchronizationContext? uiContext;
private static App? app;
// LOAD BEARING
@@ -70,8 +73,8 @@ internal sealed class Program
{
Microsoft.UI.Xaml.Application.Start((p) =>
{
Microsoft.UI.Dispatching.DispatcherQueueSynchronizationContext context = new(Microsoft.UI.Dispatching.DispatcherQueue.GetForCurrentThread());
SynchronizationContext.SetSynchronizationContext(context);
uiContext = new DispatcherQueueSynchronizationContext(DispatcherQueue.GetForCurrentThread());
SynchronizationContext.SetSynchronizationContext(uiContext);
app = new App();
});
}
@@ -94,12 +97,29 @@ internal sealed class Program
{
isRedirect = true;
PowerToysTelemetry.Log.WriteEvent(new ReactivateInstance());
keyInstance.RedirectActivationToAsync(args).AsTask().ConfigureAwait(false);
RedirectActivationTo(args, keyInstance);
}
return isRedirect;
}
private static void RedirectActivationTo(AppActivationArguments args, AppInstance keyInstance)
{
// Do the redirection on another thread, and use a non-blocking
// wait method to wait for the redirection to complete.
var redirectSemaphore = new Semaphore(0, 1);
Task.Run(() =>
{
keyInstance.RedirectActivationToAsync(args).AsTask().Wait();
redirectSemaphore.Release();
});
_ = PInvoke.CoWaitForMultipleObjects(
(uint)CWMO_FLAGS.CWMO_DEFAULT,
PInvoke.INFINITE,
[new HANDLE(redirectSemaphore.SafeWaitHandle.DangerousGetHandle())],
out _);
}
private static void OnActivated(object? sender, AppActivationArguments args)
{
// If we already have a form, display the message now.
@@ -109,9 +129,7 @@ internal sealed class Program
if (thisApp.AppWindow is not null and
MainWindow mainWindow)
{
mainWindow.HandleLaunch(args);
// mainWindow.Summon(string.Empty);
uiContext?.Post(_ => mainWindow.HandleLaunch(args), null);
}
}
}

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

@@ -106,6 +106,40 @@
</ItemsRepeater.ItemTemplate>
</ItemsRepeater>
<TextBlock
x:Uid="ExtensionFallbackCommandsHeader"
Style="{StaticResource SettingsSectionHeaderTextBlockStyle}"
Visibility="{x:Bind ViewModel.HasFallbackCommands}" />
<ItemsRepeater
ItemsSource="{x:Bind ViewModel.FallbackCommands, Mode=OneWay}"
Layout="{StaticResource VerticalStackLayout}"
Visibility="{x:Bind ViewModel.HasFallbackCommands}">
<ItemsRepeater.ItemTemplate>
<DataTemplate x:DataType="viewModels:TopLevelViewModel">
<controls:SettingsCard DataContext="{x:Bind}" Header="{x:Bind DisplayTitle, Mode=OneWay}">
<controls:SettingsCard.HeaderIcon>
<cpcontrols:ContentIcon>
<cpcontrols:ContentIcon.Content>
<cpcontrols:IconBox
Width="20"
Height="20"
AutomationProperties.AccessibilityView="Raw"
SourceKey="{x:Bind Icon, Mode=OneWay}"
SourceRequested="{x:Bind helpers:IconCacheProvider.SourceRequested}" />
</cpcontrols:ContentIcon.Content>
</cpcontrols:ContentIcon>
</controls:SettingsCard.HeaderIcon>
<!-- Content goes here -->
<ToggleSwitch IsOn="{x:Bind IsEnabled, Mode=TwoWay}" />
</controls:SettingsCard>
</DataTemplate>
</ItemsRepeater.ItemTemplate>
</ItemsRepeater>
<TextBlock
x:Uid="ExtensionSettingsHeader"
Style="{StaticResource SettingsSectionHeaderTextBlockStyle}"

View File

@@ -241,6 +241,10 @@ Right-click to remove the key combination, thereby deactivating the shortcut.</v
<value>Commands</value>
<comment>A section header for information about the app</comment>
</data>
<data name="ExtensionFallbackCommandsHeader.Text" xml:space="preserve">
<value>Fallback commands</value>
<comment>A section header for information about the commands presented to the user when the search text doesn't exactly match the name of a command.</comment>
</data>
<data name="ExtensionDisabledHeader.Text" xml:space="preserve">
<value>This extension is disabled</value>
<comment>A header to inform the user that an extension is not currently active</comment>

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;
@@ -11,13 +15,15 @@ namespace Microsoft.CmdPal.Ext.Apps;
public partial class AllAppsCommandProvider : CommandProvider
{
public const string WellKnownId = "AllApps";
public static readonly AllAppsPage Page = new();
private readonly CommandItem _listItem;
public AllAppsCommandProvider()
{
Id = "AllApps";
Id = WellKnownId;
DisplayName = Resources.installed_apps;
Icon = IconHelpers.FromRelativePath("Assets\\AllApps.svg");
Settings = AllAppsSettings.Instance.Settings;
@@ -27,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)
{
@@ -60,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;
}

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