Compare commits

..

56 Commits

Author SHA1 Message Date
leileizhang
5386572092 Update src/modules/NewPlus/NewShellExtensionContextMenu/powertoys_module.cpp
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-08-21 14:28:26 +08:00
leileizhang
0f1ccaee2d Update src/modules/imageresizer/dll/RuntimeRegistration.h
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-08-21 14:23:40 +08:00
Leilei Zhang
68b708f2fd update spelling check 2025-08-20 14:25:57 +08:00
Leilei Zhang
3088908202 use runtime register 2025-08-20 14:08:14 +08:00
Jiří Polášek
a5b9a38517 CmdPal: Bring existing Settings window to the foreground when opened (#41087)
## Summary of the Pull Request

Adds extra BringToFront after Activate. Don't ask.

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

- [x] Closes: #41086
- [ ] **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-08-13 13:48:07 -05:00
Jiří Polášek
7a3616e996 CmdPal: Replace Clipboard History extension outline icon with colorful icon (#41012)
## Summary of the Pull Request

Replace Clipboard History extension icon with an icon derived from
Fluent UI System Color set
(https://github.com/microsoft/fluentui-system-icons/). Icon is under MIT
license.

<img width="786" height="473" alt="image"
src="https://github.com/user-attachments/assets/6b9471d8-c98f-45c7-85e3-9521ce2a9717"
/>

## PR Checklist

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


## Detailed Description of the Pull Request / Additional comments


## Validation Steps Performed

I looked at the icon in the top-level list. Looks nice.
I looked at the icon in the settings page. Also looks nice.
2025-08-13 13:45:47 -05:00
Jiří Polášek
b36530bf87 CmdPal: Fix race condition in SupersedingAsyncGate cancellation handling [MSH] (#40983)
## Summary of the Pull Request

Change SetCanceled to TrySetCanceled in OperationCanceledException
handler to prevent InvalidOperationException when external and internal
cancellation tokens complete the TaskCompletionSource simultaneously.


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

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

<!-- 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-08-13 13:44:31 -05:00
Jessica Dene Earley-Cha
e260c01553 CmdPal: Setting Activation Shortcut now auto focuses on window & delivers dialog (#40968)
## Summary of the Pull Request
Screen readers now will focus on the activation shortcut windows and
read out the text

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

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


https://github.com/user-attachments/assets/d72a9aea-28b8-49d1-b51f-7a7d2a8ff42f
2025-08-13 13:43:54 -05:00
Mike Griese
7f4a97cac5 CmdPal: extension nuget should target a lower windows SDK version (#40902)
related to some #40113 work

The extension SDK shouldn't rely on a preview version of the Windows
SDK. It should use the stable one.

Also moves some messages around that we didn't need
2025-08-13 13:42:52 -05:00
Davide Giacometti
ab76dd1255 [CmdPal] Search PATH starting with ~ / \ (#40887)
<!-- 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

Love that we have now environment variables expanding! 🚀 
Porting also few small features I implemented in Run Folder plugin a
long time ago and I love:
- https://github.com/microsoft/PowerToys/pull/7711
- https://github.com/microsoft/PowerToys/pull/9579

Threat `/` and `\` as root of system drive (typically `C:\`)
Threat `~` as user home directory `%USERPROFILE%`

<!-- 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

<img width="591" height="356" alt="image"
src="https://github.com/user-attachments/assets/9c1f196a-bd4d-428c-a4e6-af9f269acd1f"
/>

<img width="591" height="356" alt="image"
src="https://github.com/user-attachments/assets/4295ebca-f12b-43b0-b3d0-c130b6faf419"
/>


<img width="591" height="356" alt="image"
src="https://github.com/user-attachments/assets/87748864-e250-4141-b366-29b45d58edcf"
/>

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

- Tested search starting with `~` `/` `\`
- Tested UNC network path starting with `\\...` and `//...`
2025-08-13 13:42:40 -05:00
Dustin L. Howett
911989bac1 store: update package catalog before running install (#41121)
It's actually failing because we're bad at... Linux?
2025-08-13 11:01:25 -05:00
Gordon Lam
c690cb1bb8 Initial draft for 0.93 release note (#41036)
<!-- 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

## AI Summary
This pull request updates the `README.md` to document the 0.93 (August
2025) release of Microsoft PowerToys. It introduces a new, modern
settings dashboard, details major improvements and new features across
multiple modules, and updates installation links and documentation. The
release focuses on enhanced user experience, accessibility, performance,
stability, and test coverage.

Most important changes:

**Release and Installation Updates**
- Updated all installer links and release references from version 0.92.1
to 0.93.0, and milestone tracking for the next release to 0.94.
- Updated the release highlights and version number to reflect the 0.93
(August 2025) release, with a summary of new features and improvements.

**Settings and User Experience**
- Introduced a completely redesigned, card-based settings dashboard with
clearer descriptions, faster navigation, and improved release notes
formatting for a better user experience.
- Rewrote setting descriptions for clarity and consistency, added deep
link support to specific settings pages, and fixed various UI/UX issues
in the settings module.

**Command Palette and Extensions**
- Resolved over 99 issues in Command Palette, including accessibility
improvements, context menu enhancements, new navigation shortcuts, AOT
compilation mode (reducing install size and memory usage), and
re-enabled Clipboard History.
- Added new settings and features to Command Palette extensions, such as
command history in Run, improved Apps extension handling, and new
context menu options.

**Module Improvements and New Features**
- Mouse Utilities: Added a new spotlight highlighting mode for
presentations.
- Peek: Added instant previews and embedded thumbnail support for Binary
G-code (.bgcode) 3D printing files.
- Quick Accent: Added Vietnamese language support.

**Development, Testing, and Documentation**
- Upgraded .NET libraries and spell check system, improved CI pipelines,
reduced test timeouts, and added over 600 new unit tests (mainly for
Command Palette), doubling UI automation coverage.
- Added detailed developer documentation, fixed broken SDK links, and
documented new community plugins.

Other minor changes:
- Standardized naming, improved spelling, and cleaned up configuration
files for smoother development.
- Minor capitalization fix for "Mouse Utilities" in the utilities table.
2025-08-13 17:19:06 +02:00
Yu Leng
c23dcb0c5a [CmdPal][UT] Refactor some cmdpal ext's ut and improve the test case (#40896)
<!-- 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
1. Remove some AI generated nonsense case
2. Add ISettingsInterface for those ext for testing purpose.
3. Add query test.

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

- [x] Closes: #40897
- [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
- [ ] **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 <yuleng@microsoft.com>
2025-08-12 18:27:10 +08:00
Kai Tao
3682f186e3 Mouse highlighter spotlight mode, fix the gpu perf issue (#41079)
<!-- 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 big border solution for implementing the spotlight mode is gpu
consuming, switch to a resource friendly implementation.

<!-- 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

| Before | After | 
|----------|----------|
| <img width="739" height="263" alt="image"
src="https://github.com/user-attachments/assets/b369801a-4cda-44e8-968b-d76586931c8c"
/>| <img width="1031" height="319" alt="image"
src="https://github.com/user-attachments/assets/9b21c96d-f5ce-4ff7-8662-0c4e6e075976"
/>|
2025-08-12 10:55:24 +08:00
Yu Leng
1eae1d9a12 [AOT] Remove rd.xml from CmdPal (#41031)
<!-- 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
Actually, we don't need to use rd.xml to preserve type. We can add
attribute to do the same things.

Tested in this build:
https://microsoft.visualstudio.com/Dart/_build/results?buildId=127875892&view=artifacts&pathAsName=false&type=publishedArtifacts

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

- [x] Closes: #40633
- [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
- [ ] **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>
2025-08-12 07:44:23 +08:00
Mike Griese
bc134b344b CmdPal: Make sure to include apps in the ctor (#41081)
A regression from #40132 

We need to set the value of `_includeApps` in the `MainListPage` ctor.
Without it, if the user doesn't have any extensions installed, the value
is never updated.
2025-08-12 07:43:08 +08:00
Jiří Polášek
04b8234192 CmdPal: Fix styles applied to MoreCommandsButton (#41059)
## Summary of the Pull Request

- Apply the same padding to the button as used for primary and secondary
command buttons.
- Use consistent spacing between keycap blocks.
- Match keycap border style and inner text brush with other command
buttons.
- Add min width constraint to shortcut keycap element to make it at
least square.

<img width="961" height="355" alt="image"
src="https://github.com/user-attachments/assets/cff5ef7e-fe67-41ac-9796-063c0e69768a"
/>


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

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

## Detailed Description of the Pull Request / Additional comments

## Validation Steps Performed
👀
2025-08-11 18:12:05 -05:00
Shawn Yuan
d72e0ab20d Fixed toggle switch not working issue. (#41049)
<!-- 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
Fixed toggle switch not working issue.

## AI Summary
This pull request refactors how `DashboardListItem` objects are created
and added to collections in the `DashboardViewModel`. The main
improvement is to separate the instantiation of each `DashboardListItem`
from the assignment of its `EnabledChangedCallback` property, which is
now set after the object is added to the relevant collection. This
change improves clarity and may help prevent issues related to object
initialization order.

Refactoring of `DashboardListItem` creation and initialization:

* In the `AddDashboardListItem` method, the `DashboardListItem` object
is now created and added to `AllModules` before its
`EnabledChangedCallback` property is set, instead of setting this
property during object initialization.
* In the `GetShortcutModules` method, both `ShortcutModules` and
`ActionModules` collections now receive `DashboardListItem` objects that
are instantiated first, added to the collection, and then have their
`EnabledChangedCallback` property set. This replaces the previous
pattern of setting the callback during object creation.
[[1]](diffhunk://#diff-aea3404667e7a3de2750bf9ab7ee8ff5e717892caa68ee1de86713cf8e21b44cL123-R136)
[[2]](diffhunk://#diff-aea3404667e7a3de2750bf9ab7ee8ff5e717892caa68ee1de86713cf8e21b44cL144-R159)
* 
<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

- [x] Closes: #41046
- [ ] **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
It is an regression from
https://github.com/microsoft/PowerToys/pull/40214

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

---------

Signed-off-by: Shuai Yuan <shuai.yuan.zju@gmail.com>
2025-08-08 22:55:00 +08:00
Kai Tao
062234c295 Settings: Mouse utils setting crash (#41050)
<!-- 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
Fix a crash in settings page due to not found converter
<!-- Please review the items on the PR checklist before submitting-->

## AI Summary
This pull request makes a small update to the `MouseUtilsPage.xaml` file
to use the correct resource for converting boolean values to visibility
states in the UI.

- Updated the `Visibility` binding on an `InfoBar` to use the
`ReverseBoolToVisibilityConverter` instead of the incorrect
`BoolToReverseVisibilityConverter` resource.

## 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
Regression caused by https://github.com/microsoft/PowerToys/pull/40214

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
<img width="1983" height="1072" alt="image"
src="https://github.com/user-attachments/assets/13c806c3-e7af-4615-a649-6d58d8fe877b"
/>
2025-08-08 16:38:46 +08:00
Jeremy Sinclair
0d4f3d851e [Deps] Update .NET packages from 9.0.7 to 9.0.8 (#41039)
<!-- 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
Updates .NET 9 Runtime / Library packages to the latest 9.0.8 servicing
release.

<!-- 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-08-08 08:28:01 +08:00
Mike Griese
e93b044f39 CmdPal: Once again, I am asking you to fix form submits (#41010)
Closes #40979

Usually, you're supposed to try to cast the action to a specific
type, and use those objects to get the data you need.
However, there's something weird with AdaptiveCards and the way it
works when we consume it when built in Release, with AOT (and
trimming) enabled. Any sort of `action.As<IAdaptiveSubmitAction>()`
or similar will throw a System.InvalidCastException.

Instead we have this horror show.

The `action.ToJson()` blob ACTUALLY CONTAINS THE `type` field, which
we can use to determine what kind of action it is. Then we can parse
the JSON manually based on the type.
2025-08-06 19:41:02 -05:00
leileizhang
fed6e523b6 Fix: used wrong preview resize event from another handler (#40995)
<!-- 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
Bug: Was using GcodePreviewResizeEvent, which will never work — switched
to use Bgcode's own event

<!-- 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

## AI Summary
This pull request makes a minor update to the event handling in the
preview pane module. The change updates the event constant used for
resizing the preview from `GcodePreviewResizeEvent` to
`BgcodePreviewResizeEvent`, likely to improve naming consistency or to
support a new event type.
2025-08-06 14:12:37 +08:00
Jiří Polášek
0997c1a013 CmdPal: Coalesce top-level commands list changes into a single task (#40943)
## Summary of the Pull Request

Self-refresh of `MainListPage` introduced in #40132 causes unnecessary
spawning of tasks by `ReapplySearchInBackground` and pushing the code
down the scenic route instead of taking shortcut.

This drop-in fix introduces a single-worker coalescing refresh loop to
eliminate thread-pool churn and syncs state in early-return paths.

## PR Checklist

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

<!-- 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-08-05 16:26:50 -05:00
Jiří Polášek
fa55cdb67f CmdPal: properly dispose of the old backdrop controller (#40986)
## Summary of the Pull Request

Properly disposes the old DesktopAcrylicController when replacing it
with a new instance.

## 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

## Detailed Description of the Pull Request / Additional comments


## Validation Steps Performed
2025-08-05 16:26:22 -05:00
Jiří Polášek
a889f4d4bd CmdPal: Update a code comment using a wrong member name [nit] (#40987)
## Summary of the Pull Request

(see title)

<!-- 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-08-05 16:26:05 -05:00
Mike Griese
281c88a620 CmdPal: fix files not having an open command (#40990)
Yea, it's that dumb.

Regressed in #40768
2025-08-05 14:15:52 -05:00
Davide Giacometti
7bcddfeb09 [PowerRename] Fix named pipe detection (#40944)
<!-- 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

Fix a regression present on master where PowerRename is activated with
empty file list where invoked Explorer context menu.
Regression was caused by
https://github.com/microsoft/PowerToys/pull/40393

- [ ] 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

Verified that PowerRename shows file list when activated:
- From Windows 11 Explorer context menu
- From Legacy Explorer context menu
- From command line passing some file paths
2025-08-05 15:44:58 +08:00
Michael Jolley
7fb4ac2dcd Adding additional descriptions for all apps settings (#40911)
Closes #38351

Adding some descriptions for all apps extension settings.

<img width="725" height="470" alt="image"
src="https://github.com/user-attachments/assets/9fb06105-80a3-4c78-b10d-241164fead11"
/>
2025-08-04 18:34:20 -05:00
Niels Laute
c91bef1517 [UX] New dashboard & refactored KeyVisual (#40214)
### Updated `KeyVisual` and `Shortcut` control
- Refactoring `KeyVisual` to remove redundant properties and UI
elements, and using Styles for better customization.
- Shortcut control now shows a "Configure shortcut" label when there's
no shortcut configured.

### Other changes
- Consolidated converters that were used across pages in `App.xaml.cs`
with consistent naming.
- Renamed templated controls (from `.cs` to `.xaml.cs`) and moving those
to the `Controls` root folder vs. individual folders for a better
overview.
<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

Closes #39520
Closes #32944

---------

Co-authored-by: Jay <65828559+Jay-o-Way@users.noreply.github.com>
Co-authored-by: Jaylyn Barbee <51131738+Jaylyn-Barbee@users.noreply.github.com>
2025-08-04 18:33:19 -05:00
leileizhang
fdd1f47d85 [UI tests] Fix UI test pipeline to properly handle buildSource parameter conditions (#40899)
<!-- 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 a logic issue in the UI test automation pipeline where selecting
`latestMainOfficialBuild` would still trigger a full PowerToys build
instead of only building UI tests.

### Why
The pipeline was using template variables in compilation-time
conditions, which doesn't work correctly in pipeline

### fix
Replace template variable references with direct parameter comparisons
in compilation-time conditions

<!-- 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-08-01 13:57:39 +08:00
leileizhang
9a998b2056 [UI Tests] Enhance UI Test Automation and Pipeline Support for CmdPal Module (#40871)
<!-- 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 pull request introduces enhancements to UI test automation,
improvements to pipeline configuration, and project structure updates.
The goal is to improve flexibility, maintainability, and efficiency in
PowerToys’ CI/CD processes.

### UI Test Enhancements:
Delayed Text Input Support
- UI tests now support character-by-character text input with
configurable delays.
- This serves as a workaround for a known CmdPal bug where input is
swallowed too quickly. The delay mitigates the issue until it is fixed
in CmdPal.
 
Centralized Environment Management
- Introduced a new class to centralize environment variable access for
UI test configuration.

CmdPal Launch Handling in Pipelines

- Adjusted test logic to handle CmdPal module startup specifically in CI
pipelines



### Pipeline Configuration Updates:
Build Artifact Customization

- Included test-related folders in pipeline build outputs for better
traceability.

Support for Build ID Targeting

- Added support for specifying PowerToys build IDs in test pipelines,
with conditional logic for specific or latest build selection.
<img width="264" height="44" alt="image"
src="https://github.com/user-attachments/assets/0d68a51e-e41a-4868-a1c3-f4233c56b0ee"
/>


### Project Structure Updates:
Added Peek.UITests back to the solution which removed by
https://github.com/microsoft/PowerToys/pull/40754

<!-- 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-31 13:55:23 +08:00
Kai Tao
d26ef36e31 [Doc] Add doc for a script to build installer locally, and doc for testing winget install locally (#40805)
<!-- 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

- [ ] 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
1. Add instructions to build installer locally with a script
2. Add doc explaining how to install an installer by winget locally.
<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2025-07-31 09:48:24 +08:00
Jiří Polášek
ee6336c47d Change filter box placeholder for main list page only (#40799)
## Summary of the Pull Request

Changes the placeholder in the filter box only on the main list page to
"Search for apps, files and commands...":
<img width="786" height="473" alt="image"
src="https://github.com/user-attachments/assets/844d27ae-61cf-42c9-a7f6-ae78817e928c"
/>

The default value remains unchanged as "Type here to search..." for all
other pages (both built-in and internal), unless the author overrides
it:
<img width="786" height="473" alt="image"
src="https://github.com/user-attachments/assets/aeb3500b-9e36-4e35-8dd7-3bd226336823"
/>

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

- [x] Closes: #40763
- [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:** Added/updated
- [x] **New binaries:** none
- [x] **Documentation updated:** 

## Detailed Description of the Pull Request / Additional comments

## Validation Steps Performed
2025-07-30 09:27:02 -07:00
Yu Leng
46d380c2b6 [CmdPal][UnitTest] Refactor system command unit test (#40874)
<!-- 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
Ok... The AI generated and migrated ut's quality is very poor. We need
to refactor it.

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

- [x] Closes: #40875
- [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
- [ ] **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>
2025-07-30 17:19:40 +08:00
Jiří Polášek
decb947283 CmdPal: Replace Tapped events with generic ones (#40640)
## Summary of the Pull Request
Click event on WinUI buttons handle more than just click and is more
versatile that Tapped event. When you tap a Button with a finger or
stylus, or press a left mouse button while the pointer is over it, the
button raises the Click event. If a button has keyboard focus, pressing
the Enter key or the Spacebar key also raises the Click event.

This PR also replaces the right-tapped event on items on the list page
with context menu handling, allowing other input gestures (such as
Shift+F10) to also display the context menu.

And finally, it adds a button to the status messages badge so that the
flyout can be opened using the keyboard.

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

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

Tested on desktop with keyboard and mouse (no cats), and SB2 with touch
and pen. Input gestures seem to work as intended.

---------

Co-authored-by: Mike Griese <migrie@microsoft.com>
2025-07-29 17:58:39 +08:00
Kai Tao
801fad09ba Fix a settings crash due to incompatible property name (#40854)
<!-- 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

- [ ] 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
- [ ] **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
This pull request introduces a minor update to the `ZoomItProperties`
class in the `Settings.UI.Library` project. The change adds a new
property, `AnimateZoom`, with a JSON property name annotation.

*
[`src/settings-ui/Settings.UI.Library/ZoomItProperties.cs`](diffhunk://#diff-2cd3f90110c7ba387a449d246b4949c3f6cf7f746865f327dbb70f01feeb0cf1R81):
Added a new `BoolProperty` named `AnimateZoom` with a
`[JsonPropertyName("AnimnateZoom")]` attribute.
- [ ] [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
Locally checked, no broken any more
2025-07-29 10:53:41 +08:00
Mike Griese
5f2e446f3b cmdpal: move kb shortcut handling to PreviewKeyDown (#40777)
This lets things like C-S-c work in the text box, and in the context
menu too

Closes #40174
2025-07-28 20:17:27 -05:00
Mike Griese
3a0487f74a cmdpal: Add "file" context items to the run items too (#40768)
After #39955, the "exe" items from the shell commands only ever have the
"Run{as admin, as other user}" commands. This adds the rest of the
"file" commands - copy path, open in explorer, etc.

This shuffles around some commands into the toolkit and common commands
project to make this easier.

<img width="814" height="505" alt="image"
src="https://github.com/user-attachments/assets/36ae2c75-d4d6-4762-98ec-796986f39c20"
/>
2025-07-28 20:03:49 -05:00
Mike Griese
6dc2d14e13 CmdPal: A different approach to bookmarking scripts, exes (try 2) (#40758)
_⚠️ targets #40427_ 

This is a different approach to #39059 that I was thinking about like a
month ago. It builds on the work from the rejuv'd run page (#39955) to
process the bookmark as an exe/path/url automatically.

I need to cross-check this with #39059 - I haven't cached that back in
since I got back from leave. I remember thinking that I wanted to try
this approach, but wasn't sure if it was right. More than anything, I
want to get it off my local PC and out for discussion

* We don't need to manually store the type anymore. 
* breaking change: paths with a space do need to be wrapped in spaces

closes #38700

----

I accidentally destroyed #40430 with a fat-finger merge from #40427 into
it. This resurrects that PR
2025-07-28 18:52:25 -05:00
Jiří Polášek
7bd9d973cf CmdPal: Sync access to TopLevelCommandManager from UpdateCommandsForProvider (#40752)
## Summary of the Pull Request

Fixes unsynchronized access to `LoadTopLevelCommands` in
`TopLevelCommandManager.UpdateCommandsForProvider`, which previously led
to `InvalidOperationException: Collection was modified`.

Addressing this also uncovered another issue: overlapping invocations of
`ReloadAllCommandsAsync` were causing duplication of items in the main
list -- so I'm fixing that as well.

## PR Checklist

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

## Detailed Description of the Pull Request / Additional comments

## Validation Steps Performed
Tested with bookmarks.
2025-07-28 18:51:57 -05:00
Mike Griese
abc812e579 CmdPal: Fix paths to dirs on the Run fallback (#40850)
We were being too clever with `\`; and yet simultaneously not clever
enough.
* When we saw `c:\users`, we'd treat that as a path with a Title
`users\`
* but when we saw `c:\users\`, we'd fail to find a file name, and the
just treat the name as `\`. That was dumb.
* And we'd add trailing `\`'s even if there already was one.
* But then if the user typed `c:\users`, we would immediately start
enumerating children of that dir, which didn't really feel right

This PR fixes all of that.

Closes #40797
2025-07-28 18:50:33 -05:00
Mike Griese
325b1a1441 CmdPal: Remove vestigial try/catch (#40815)
This was added in #38040 but appears to be vestigial now.

RE: #40113
2025-07-28 18:47:18 -05:00
Mike Griese
8829bbac16 CmdPal: Move the OpenContextMenuMessage into the UI project (#40791)
I just blindly moved all the messages. But _this_ one really makes more
sense as a UI message. It's got framework elements. It us used to
actually open a UI element. The whole thing is very UI specific.

re: #40113
2025-07-28 18:46:16 -05:00
Mike Griese
480a2db0cd CmdPal: Bump our package version to 0.4 (#40852)
title

also adds our pdb to the nuget package.
2025-07-28 18:45:16 -05:00
Mike Griese
db9d7a8804 CmdPal: fix handling form submits (#40847)
Yea this was real dumb.

I removed the `HandleCommandResultMessage` handler from `ShellPage`, and
never put it on `ShellViewModel`. Just first-grade kind of mistake.

Closes #40776
Regressed in #40479
re: #40113
2025-07-28 18:42:55 -05:00
Michael Jolley
c16cd4c96f Fixed issue with primary/secondary commands (#40849)
Closes #40822

These are not the classes you are looking for.

Issue was we were comparing to classes rather than interfaces and WinRT
no likey.
2025-07-28 18:26:32 -05:00
Jiří Polášek
4785af2425 CmdPal: Handle exceptions when enqueuing callbacks to UI thread in IconCacheService (#40716)
## Summary of the Pull Request

Handle exceptions thrown in TryEnqueue callbacks so they don’t crash the
app (as they cannot be caught by the global exception handler). Any
exceptions are now returned to the caller for handling. Additionally, a
failure to enqueue the operation onto the dispatcher will also result in
an exception.

This is not a breaking change, as exceptions only propagate within the
class and do not affect external callers.

Ref: #38260

## PR Checklist

- [ ] **Closes:** #38260 
- [ ] **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-28 16:41:27 -05:00
Jiří Polášek
f81802430c CmdPal: Handle CommandItem Title changes properly and raise notification every time it changes (#40513)
<!-- 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:** #39167 
- [ ] **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-28 16:40:29 -05:00
Jiří Polášek
4489677b64 CmdPal: Add error handling to extension disposal (#40825)
## Summary of the Pull Request

Ensure that errors encountered while sending the extension disposal
signal are handled gracefully. If an error occurs when disposing of a
particular extension, continue signaling the remaining extensions rather
than halting the entire process. This prevents a single failure from
interrupting the disposal chain and improves overall robustness.

<!-- 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-28 16:18:04 -05:00
Jessica Dene Earley-Cha
6242401b40 add AutomationNotification for screen readers (#40761)
## Summary of the Pull Request

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

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


## Detailed Description of the Pull Request / Additional comments

Add AutomationNotification to ItemsList_SelectionChanged so that when
user uses keyboard navigation, it sends the title to be read by the
screen reader


## Validation Steps Performed


https://github.com/user-attachments/assets/34a11e55-18ce-440f-97d8-e6ea60c57f78

Co-authored-by: Mike Griese <migrie@microsoft.com>
2025-07-28 09:56:01 -05:00
VictorNoxx
c10f2c54ba Update thirdPartyRunPlugins.md (#40790)
```markdown
# PowerToys Run: Add Cursor AI Plugin to Third-Party Plugins List

## Summary of the Pull Request

This PR adds the "Open With Cursor" plugin to the `thirdPartyRunPlugins.md` documentation. The plugin enables users to quickly open Visual Studio and VS Code recent workspaces directly in Cursor AI editor through PowerToys Run launcher.

The plugin provides:
- Quick access to recent VS/VSCode workspaces
- Integration with Cursor AI editor
- PowerToys Run launcher compatibility
- Support for various workspace types (local, WSL, SSH, remote)

## PR Checklist

- [ ] **Closes:** N/A (Documentation update)
- [x] **Communication:** This is a documentation update to list an existing third-party plugin
- [ ] **Tests:** N/A (Documentation change only)
- [ ] **Localization:** N/A (English documentation only)
- [x] **Dev docs:** Updated thirdPartyRunPlugins.md with new plugin entry
- [ ] **New binaries:** N/A (Third-party plugin, not included in PowerToys distribution)
- [ ] **Documentation updated:** This PR updates the documentation

## Detailed Description of the Pull Request / Additional comments
```

**Added entry to thirdPartyRunPlugins.md:**

| [Open With
Cursor](https://github.com/VictorNoxx/PowerToys-Run-Cursor/) |
[VictorNoxx](https://github.com/VictorNoxx) | Open Visual Studio, VS
Code recents with Cursor AI |


**Plugin Details:**
- **Repository:** https://github.com/VictorNoxx/PowerToys-Run-Cursor/
- **Author:** [@VictorNoxx](https://github.com/VictorNoxx)
- **License:** MIT
- **Functionality:** Integrates with PowerToys Run to open recent Visual
Studio and VS Code workspaces directly in Cursor AI editor
- **Inspiration:** Based on the community request from [Issue
#3547](https://github.com/microsoft/PowerToys/issues/3547) and inspired
by [@davidegiacometti's Visual Studio
plugin](https://github.com/davidegiacometti/PowerToys-Run-VisualStudio)

**Technical Implementation:**
- Uses `vswhere.exe` for Visual Studio instance detection
- Parses workspace configuration files and recent project lists
- Direct command-line integration with Cursor AI
- Supports multiple workspace types and remote development scenarios

This plugin fills a gap for developers using Cursor AI who want quick
access to their recent projects without manually navigating through
folders or opening multiple applications.

## Validation Steps Performed

- [x] Verified the plugin repository exists and is publicly accessible
- [x] Confirmed the plugin has proper documentation and README
- [x] Tested the markdown formatting in the documentation
- [x] Verified all links are working correctly
- [x] Confirmed the plugin description accurately reflects functionality
- [x] Checked that the entry follows the same format as other entries in
the list
```
2025-07-28 09:55:26 -05:00
Jiří Polášek
498fe75c4a CmdPal: Avoid reentrancy issues when loading more items (#40715)
## Summary of the Pull Request
When checking the HasMoreItems flag, COM can start a nested message
pump, which allows a reentrant call on the XAML UI and causes a fast
fail. This change moves the check off the UI thread to prevent
reentrancy, but the loading flag is set before we know for sure that
there is something to load.

This update also introduces a change: if LoadMore fails, we clear the
loading flag immediately.

## PR Checklist

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

## Detailed Description of the Pull Request / Additional comments

## Validation Steps Performed
2025-07-28 09:06:23 -05:00
Michael Jolley
114c3972be CmdPal: Filtering out pinned apps on search (#40785)
Closes #40781 

Filters out TopLevelCommands whose Id matches an app coming from the
`AllAppsCommandProvider.Page.GetItems()`.

Hate adding processing there, but without adding some type of `bool
HideMeOnSearch` to something low enough (like ICommandItem), I don't see
another way to distinguish these.
2025-07-28 08:45:08 -05:00
Mike Griese
858081ec78 CmdPal: try to fix the context menu crash, again. (#40814)
Cherry-pick of 782ee47. That is probably over-aggressive, but it fixes
it.

Closes  #40633
previously: #40744

---------

Co-authored-by: Yu Leng (from Dev Box) <yuleng@microsoft.com>
2025-07-28 08:32:08 -05:00
Mike Griese
81a7b81927 CmdPal: fix content on extension settings (#40794)
Regressed in one of the #40113 prs.

The Core.VM.PageViewModelFactory didn't actually know how to make a
ContentPage, because Core can't handle a FormContent.

But really, the CommandSettingsViewModel shouldn't have ever been in the
.Core namespace, nor should it have used the ContentPageViewModel.

To prevent future mistakes like this,
* I got rid of `Core.ViewModels/PageViewModelFactory`, cause we didn't
need it.
* I made `ContentPageViewModel` abstract, to prevent you from trying to
instantiate it

Closes #40778
2025-07-28 06:44:25 -05:00
Brian James
dba7be2619 [CmdPal] Replaced ellipsis button with 'More' label + shortcut textblock (#39838)
This PR improves the command bar UI in the command palette.
- Replaced ellipses "..." with a "More" label (can be changed to
something else. Maybe "Actions" or "Commands")
- Added a textblock for Ctrl + K shortcut
- Removed tooltip that showed Ctrl + K shortcut when hovering over
previous "..."

Special Note:
- The InfoBar.Severity binding was temporarily commented out because of
a built-time error even though 'State' property is present in the
ViewModel. Happy to revisit this if the team can help/confirm the
intended binding context/behavior

Before change:

![image](https://github.com/user-attachments/assets/5bcb171b-7c09-4fce-a39e-38c5ac8988e3)

After change:

![added_cmdpal_label](https://github.com/user-attachments/assets/38d1ccd8-3d39-42d2-9c15-79028d2018e5)

Closes #39501

---------

Co-authored-by: Michael Jolley <mike@baldbeardedbuilder.com>
2025-07-25 15:03:16 -05:00
258 changed files with 5685 additions and 3583 deletions

View File

@@ -288,3 +288,6 @@ CACHEWRITE
MRUCMPPROC
MRUINFO
REGSTR
# Misc Win32 APIs and PInvokes
INVOKEIDLIST

View File

@@ -1440,6 +1440,7 @@ secpol
securestring
SEEMASKINVOKEIDLIST
SELCHANGE
selfhost
SENDCHANGE
sendvirtualinput
serverside
@@ -1879,6 +1880,7 @@ winexe
winforms
winget
wingetcreate
wingetpkgs
Winhook
WINL
winlogon

View File

@@ -17,7 +17,7 @@ jobs:
steps:
- name: BODGY - Set up Gnome Keyring for future Cert Auth
run: |-
sudo apt-get install -y gnome-keyring
sudo apt-get update && sudo apt-get install -y gnome-keyring
export $(dbus-launch --sh-syntax)
export $(echo 'anypass_just_to_unlock' | gnome-keyring-daemon --unlock)
export $(echo 'anypass_just_to_unlock' | gnome-keyring-daemon --start --components=gpg,pkcs11,secrets,ssh)

View File

@@ -123,7 +123,7 @@ jobs:
displayName: Stage UI Test Build Outputs
inputs:
sourceFolder: '$(Build.SourcesDirectory)'
contents: '$(BuildPlatform)/$(BuildConfiguration)/**/*'
contents: '**/$(BuildPlatform)/$(BuildConfiguration)/tests/**/*'
targetFolder: '$(JobOutputDirectory)\$(BuildPlatform)\$(BuildConfiguration)'
- publish: $(JobOutputDirectory)

View File

@@ -11,12 +11,14 @@ parameters:
- name: useLatestWebView2
type: boolean
default: false
- name: useLatestOfficialBuild
type: boolean
default: true
- name: useCurrentBranchBuild
type: boolean
default: false
- name: buildSource
type: string
default: "latestMainOfficialBuild"
displayName: "Build Source"
- name: specificBuildId
type: string
default: "xxxx"
displayName: "Build ID (for specific builds)"
- name: uiTestModules
type: object
default: []
@@ -113,16 +115,17 @@ jobs:
& '$(build.sourcesdirectory)\.pipelines\InstallWinAppDriver.ps1'
displayName: Download and install WinAppDriver
- ${{ if eq(parameters.useLatestOfficialBuild, true) }}:
- ${{ if ne(parameters.buildSource, 'buildNow') }}:
- task: DownloadPipelineArtifact@2
inputs:
buildType: 'specific'
project: 'Dart'
definition: '76541'
buildVersionToDownload: 'latestFromBranch'
${{ if eq(parameters.useCurrentBranchBuild, true) }}:
branchName: '$(Build.SourceBranch)'
${{ if eq(parameters.buildSource, 'specificBuildId') }}:
buildVersionToDownload: 'specific'
buildId: '${{ parameters.specificBuildId }}'
${{ else }}:
buildVersionToDownload: 'latestFromBranch'
branchName: 'refs/heads/main'
artifactName: 'build-$(BuildPlatform)-Release'
targetPath: '$(Build.ArtifactStagingDirectory)'
@@ -133,7 +136,7 @@ jobs:
patterns: |
**/PowerToysSetup*.exe
- ${{ if eq(parameters.useLatestOfficialBuild, true) }}:
- ${{ if ne(parameters.buildSource, 'buildNow') }}:
- ${{ if eq(parameters.installMode, 'peruser') }}:
- pwsh: |-
& "$(build.sourcesdirectory)\.pipelines\installPowerToys.ps1" -InstallMode "PerUser"
@@ -169,7 +172,7 @@ jobs:
!**\UITests-FancyZones\**\UITests-FancyZonesEditor.dll
env:
platform: '$(TestPlatform)'
useInstallerForTest: ${{ parameters.useLatestOfficialBuild }}
useInstallerForTest: ${{ ne(parameters.buildSource, 'buildNow') }}
- ${{ if ne(length(parameters.uiTestModules), 0) }}:
- ${{ each module in parameters.uiTestModules }}:
@@ -191,4 +194,4 @@ jobs:
!**\UITests-FancyZones\**\UITests-FancyZonesEditor.dll
env:
platform: '$(TestPlatform)'
useInstallerForTest: ${{ parameters.useLatestOfficialBuild }}
useInstallerForTest: ${{ ne(parameters.buildSource, 'buildNow') }}

View File

@@ -19,155 +19,40 @@ 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: buildSource
type: string
default: "latestMainOfficialBuild"
displayName: "Build Source"
values:
- latestMainOfficialBuild
- buildNow
- specificBuildId
- name: specificBuildId
type: string
default: 'xxxx'
displayName: "Build ID (only used when Build Source = specificBuildId)"
- name: uiTestModules
type: object
default: []
stages:
- ${{ each platform in parameters.buildPlatforms }}:
- ${{ 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
# Full build path: build PowerToys + UI tests + run tests
- ${{ if eq(parameters.buildSource, 'buildNow') }}:
- template: pipeline-ui-tests-full-build.yml
parameters:
platform: ${{ platform }}
enableMsBuildCaching: ${{ parameters.enableMsBuildCaching }}
useVSPreview: ${{ parameters.useVSPreview }}
useLatestWebView2: ${{ parameters.useLatestWebView2 }}
uiTestModules: ${{ parameters.uiTestModules }}
- ${{ 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
${{ 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
${{ 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 }}
${{ 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'
# Official build path: build UI tests only + download official build + run tests
- ${{ if ne(parameters.buildSource, 'buildNow') }}:
- template: pipeline-ui-tests-official-build.yml
parameters:
platform: ${{ platform }}
buildSource: ${{ parameters.buildSource }}
specificBuildId: ${{ parameters.specificBuildId }}
useLatestWebView2: ${{ parameters.useLatestWebView2 }}
uiTestModules: ${{ parameters.uiTestModules }}

View File

@@ -0,0 +1,80 @@
# Template for full build path: Build PowerToys + Build UI Tests + Run Tests
parameters:
- name: platform
type: string
- name: enableMsBuildCaching
type: boolean
default: false
- name: useVSPreview
type: boolean
default: false
- name: useLatestWebView2
type: boolean
default: false
- name: uiTestModules
type: object
default: []
stages:
# Stage 1: Build full PowerToys project
- stage: Build_${{ parameters.platform }}
displayName: Build PowerToys ${{ parameters.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:
- ${{ parameters.platform }}
buildConfigurations: [Release]
enablePackageCaching: true
enableMsBuildCaching: ${{ parameters.enableMsBuildCaching }}
runTests: false
buildTests: true
useVSPreview: ${{ parameters.useVSPreview }}
timeoutInMinutes: 90
# Stage 2: Run UI Tests
- ${{ if eq(parameters.platform, 'x64') }}:
- stage: Test_x64Win10_FullBuild
displayName: Test x64Win10 (Full Build)
dependsOn: Build_${{ parameters.platform }}
jobs:
- template: job-test-project.yml
parameters:
platform: x64Win10
configuration: Release
useLatestWebView2: ${{ parameters.useLatestWebView2 }}
buildSource: 'buildNow'
uiTestModules: ${{ parameters.uiTestModules }}
- stage: Test_x64Win11_FullBuild
displayName: Test x64Win11 (Full Build)
dependsOn: Build_${{ parameters.platform }}
jobs:
- template: job-test-project.yml
parameters:
platform: x64Win11
configuration: Release
useLatestWebView2: ${{ parameters.useLatestWebView2 }}
buildSource: 'buildNow'
uiTestModules: ${{ parameters.uiTestModules }}
- ${{ if ne(parameters.platform, 'x64') }}:
- stage: Test_${{ parameters.platform }}_FullBuild
displayName: Test ${{ parameters.platform }} (Full Build)
dependsOn: Build_${{ parameters.platform }}
jobs:
- template: job-test-project.yml
parameters:
platform: ${{ parameters.platform }}
configuration: Release
useLatestWebView2: ${{ parameters.useLatestWebView2 }}
buildSource: 'buildNow'
uiTestModules: ${{ parameters.uiTestModules }}

View File

@@ -0,0 +1,110 @@
# Template for official build path: Download Official Build + Build UI Tests Only + Run Tests
parameters:
- name: platform
type: string
- name: buildSource
type: string
- name: specificBuildId
type: string
default: 'xxxx'
- name: useLatestWebView2
type: boolean
default: false
- name: uiTestModules
type: object
default: []
stages:
# Stage 1: Build UI Tests Only
- stage: BuildUITests_${{ parameters.platform }}
displayName: Build UI Tests Only ${{ parameters.platform }}
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
buildPlatforms:
- ${{ parameters.platform }}
uiTestModules: ${{ parameters.uiTestModules }}
# Stage 2: Run UI Tests with Official Build
- ${{ if eq(parameters.platform, 'x64') }}:
- stage: Test_x64Win10_OfficialBuild
displayName: Test x64Win10 (Official Build)
dependsOn: BuildUITests_${{ parameters.platform }}
jobs:
- template: job-test-project.yml
parameters:
platform: x64Win10
configuration: Release
useLatestWebView2: ${{ parameters.useLatestWebView2 }}
buildSource: ${{ parameters.buildSource }}
specificBuildId: ${{ parameters.specificBuildId }}
uiTestModules: ${{ parameters.uiTestModules }}
# Additional per-user installation test
- template: job-test-project.yml
parameters:
platform: x64Win10
configuration: Release
useLatestWebView2: ${{ parameters.useLatestWebView2 }}
buildSource: ${{ parameters.buildSource }}
specificBuildId: ${{ parameters.specificBuildId }}
uiTestModules: ${{ parameters.uiTestModules }}
installMode: 'peruser'
jobSuffix: '_PerUser'
- stage: Test_x64Win11_OfficialBuild
displayName: Test x64Win11 (Official Build)
dependsOn: BuildUITests_${{ parameters.platform }}
jobs:
- template: job-test-project.yml
parameters:
platform: x64Win11
configuration: Release
useLatestWebView2: ${{ parameters.useLatestWebView2 }}
buildSource: ${{ parameters.buildSource }}
specificBuildId: ${{ parameters.specificBuildId }}
uiTestModules: ${{ parameters.uiTestModules }}
# Additional per-user installation test
- template: job-test-project.yml
parameters:
platform: x64Win11
configuration: Release
useLatestWebView2: ${{ parameters.useLatestWebView2 }}
buildSource: ${{ parameters.buildSource }}
specificBuildId: ${{ parameters.specificBuildId }}
uiTestModules: ${{ parameters.uiTestModules }}
installMode: 'peruser'
jobSuffix: '_PerUser'
- ${{ if ne(parameters.platform, 'x64') }}:
- stage: Test_${{ parameters.platform }}_OfficialBuild
displayName: Test ${{ parameters.platform }} (Official Build)
dependsOn: BuildUITests_${{ parameters.platform }}
jobs:
- template: job-test-project.yml
parameters:
platform: ${{ parameters.platform }}
configuration: Release
useLatestWebView2: ${{ parameters.useLatestWebView2 }}
buildSource: ${{ parameters.buildSource }}
specificBuildId: ${{ parameters.specificBuildId }}
uiTestModules: ${{ parameters.uiTestModules }}
# Additional per-user installation test
- template: job-test-project.yml
parameters:
platform: ${{ parameters.platform }}
configuration: Release
useLatestWebView2: ${{ parameters.useLatestWebView2 }}
buildSource: ${{ parameters.buildSource }}
specificBuildId: ${{ parameters.specificBuildId }}
uiTestModules: ${{ parameters.uiTestModules }}
installMode: 'peruser'
jobSuffix: '_PerUser'

View File

@@ -34,22 +34,22 @@
<!-- Including MessagePack to force version, since it's used by StreamJsonRpc but contains vulnerabilities. After StreamJsonRpc updates the version of MessagePack, we can upgrade StreamJsonRpc instead. -->
<PackageVersion Include="MessagePack" Version="3.1.3" />
<PackageVersion Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="9.0.0" />
<PackageVersion Include="Microsoft.Data.Sqlite" Version="9.0.7" />
<PackageVersion Include="Microsoft.Data.Sqlite" Version="9.0.8" />
<!-- Including Microsoft.Bcl.AsyncInterfaces to force version, since it's used by Microsoft.SemanticKernel. -->
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="9.0.7" />
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="9.0.8" />
<PackageVersion Include="Microsoft.Diagnostics.Tracing.TraceEvent" Version="3.1.16" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="9.0.7" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="9.0.7" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.7" />
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="9.0.7" />
<PackageVersion Include="Microsoft.Extensions.Hosting.WindowsServices" Version="9.0.7" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="9.0.8" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="9.0.8" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.8" />
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="9.0.8" />
<PackageVersion Include="Microsoft.Extensions.Hosting.WindowsServices" Version="9.0.8" />
<PackageVersion Include="Microsoft.SemanticKernel" Version="1.15.0" />
<PackageVersion Include="Microsoft.Toolkit.Uwp.Notifications" Version="7.1.2" />
<PackageVersion Include="Microsoft.Web.WebView2" Version="1.0.2903.40" />
<!-- Package Microsoft.Win32.SystemEvents added as a hack for being able to exclude the runtime assets so they don't conflict with 8.0.1. This is a dependency of System.Drawing.Common but the 8.0.1 version wasn't published to nuget. -->
<PackageVersion Include="Microsoft.Win32.SystemEvents" Version="9.0.7" />
<PackageVersion Include="Microsoft.Win32.SystemEvents" Version="9.0.8" />
<PackageVersion Include="Microsoft.WindowsPackageManager.ComInterop" Version="1.10.340" />
<PackageVersion Include="Microsoft.Windows.Compatibility" Version="9.0.7" />
<PackageVersion Include="Microsoft.Windows.Compatibility" Version="9.0.8" />
<PackageVersion Include="Microsoft.Windows.CsWin32" Version="0.3.183" />
<!-- CsWinRT version needs to be set to have a WinRT.Runtime.dll at the same version contained inside the NET SDK we're currently building on CI. -->
<!--
@@ -78,28 +78,28 @@
<PackageVersion Include="StreamJsonRpc" Version="2.21.69" />
<PackageVersion Include="StyleCop.Analyzers" Version="1.2.0-beta.556" />
<!-- Package System.CodeDom added as a hack for being able to exclude the runtime assets so they don't conflict with 8.0.1. This is a dependency of System.Management but the 8.0.1 version wasn't published to nuget. -->
<PackageVersion Include="System.CodeDom" Version="9.0.7" />
<PackageVersion Include="System.CodeDom" Version="9.0.8" />
<PackageVersion Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
<PackageVersion Include="System.ComponentModel.Composition" Version="9.0.7" />
<PackageVersion Include="System.Configuration.ConfigurationManager" Version="9.0.7" />
<PackageVersion Include="System.Data.OleDb" Version="9.0.7" />
<PackageVersion Include="System.ComponentModel.Composition" Version="9.0.8" />
<PackageVersion Include="System.Configuration.ConfigurationManager" Version="9.0.8" />
<PackageVersion Include="System.Data.OleDb" Version="9.0.8" />
<!-- Package System.Data.SqlClient added to force it as a dependency of Microsoft.Windows.Compatibility to the latest version available at this time. -->
<PackageVersion Include="System.Data.SqlClient" Version="4.9.0" />
<!-- Package System.Diagnostics.EventLog added as a hack for being able to exclude the runtime assets so they don't conflict with 8.0.1. This is a dependency of System.Data.OleDb but the 8.0.1 version wasn't published to nuget. -->
<PackageVersion Include="System.Diagnostics.EventLog" Version="9.0.7" />
<PackageVersion Include="System.Diagnostics.EventLog" Version="9.0.8" />
<!-- Package System.Diagnostics.PerformanceCounter added as a hack for being able to exclude the runtime assets so they don't conflict with 8.0.11. -->
<PackageVersion Include="System.Diagnostics.PerformanceCounter" Version="9.0.7" />
<PackageVersion Include="System.Drawing.Common" Version="9.0.7" />
<PackageVersion Include="System.Diagnostics.PerformanceCounter" Version="9.0.8" />
<PackageVersion Include="System.Drawing.Common" Version="9.0.8" />
<PackageVersion Include="System.IO.Abstractions" Version="22.0.13" />
<PackageVersion Include="System.IO.Abstractions.TestingHelpers" Version="22.0.13" />
<PackageVersion Include="System.Management" Version="9.0.7" />
<PackageVersion Include="System.Management" Version="9.0.8" />
<PackageVersion Include="System.Net.Http" Version="4.3.4" />
<PackageVersion Include="System.Private.Uri" Version="4.3.2" />
<PackageVersion Include="System.Reactive" Version="6.0.1" />
<PackageVersion Include="System.Runtime.Caching" Version="9.0.7" />
<PackageVersion Include="System.ServiceProcess.ServiceController" Version="9.0.7" />
<PackageVersion Include="System.Text.Encoding.CodePages" Version="9.0.7" />
<PackageVersion Include="System.Text.Json" Version="9.0.7" />
<PackageVersion Include="System.Runtime.Caching" Version="9.0.8" />
<PackageVersion Include="System.ServiceProcess.ServiceController" Version="9.0.8" />
<PackageVersion Include="System.Text.Encoding.CodePages" Version="9.0.8" />
<PackageVersion Include="System.Text.Json" Version="9.0.8" />
<PackageVersion Include="System.Text.RegularExpressions" Version="4.3.1" />
<PackageVersion Include="UnicodeInformation" Version="2.6.0" />
<PackageVersion Include="UnitsNet" Version="5.56.0" />

View File

@@ -1519,23 +1519,23 @@ SOFTWARE.
- Mages 3.0.0
- Markdig.Signed 0.34.0
- MessagePack 3.1.3
- Microsoft.Bcl.AsyncInterfaces 9.0.7
- Microsoft.Bcl.AsyncInterfaces 9.0.8
- Microsoft.Bot.AdaptiveExpressions.Core 4.23.0
- Microsoft.CodeAnalysis.NetAnalyzers 9.0.0
- Microsoft.Data.Sqlite 9.0.7
- Microsoft.Data.Sqlite 9.0.8
- Microsoft.Diagnostics.Tracing.TraceEvent 3.1.16
- Microsoft.DotNet.ILCompiler (A)
- Microsoft.Extensions.DependencyInjection 9.0.7
- Microsoft.Extensions.Hosting 9.0.7
- Microsoft.Extensions.Hosting.WindowsServices 9.0.7
- Microsoft.Extensions.Logging 9.0.7
- Microsoft.Extensions.Logging.Abstractions 9.0.7
- Microsoft.Extensions.DependencyInjection 9.0.8
- Microsoft.Extensions.Hosting 9.0.8
- Microsoft.Extensions.Hosting.WindowsServices 9.0.8
- Microsoft.Extensions.Logging 9.0.8
- Microsoft.Extensions.Logging.Abstractions 9.0.8
- Microsoft.NET.ILLink.Tasks (A)
- Microsoft.SemanticKernel 1.15.0
- Microsoft.Toolkit.Uwp.Notifications 7.1.2
- Microsoft.Web.WebView2 1.0.2903.40
- Microsoft.Win32.SystemEvents 9.0.7
- Microsoft.Windows.Compatibility 9.0.7
- Microsoft.Win32.SystemEvents 9.0.8
- Microsoft.Windows.Compatibility 9.0.8
- Microsoft.Windows.CsWin32 0.3.183
- Microsoft.Windows.CsWinRT 2.2.0
- Microsoft.Windows.SDK.BuildTools 10.0.26100.4188
@@ -1555,25 +1555,25 @@ SOFTWARE.
- SkiaSharp.Views.WinUI 2.88.9
- StreamJsonRpc 2.21.69
- StyleCop.Analyzers 1.2.0-beta.556
- System.CodeDom 9.0.7
- System.CodeDom 9.0.8
- System.CommandLine 2.0.0-beta4.22272.1
- System.ComponentModel.Composition 9.0.7
- System.Configuration.ConfigurationManager 9.0.7
- System.Data.OleDb 9.0.7
- System.ComponentModel.Composition 9.0.8
- System.Configuration.ConfigurationManager 9.0.8
- System.Data.OleDb 9.0.8
- System.Data.SqlClient 4.9.0
- System.Diagnostics.EventLog 9.0.7
- System.Diagnostics.PerformanceCounter 9.0.7
- System.Drawing.Common 9.0.7
- System.Diagnostics.EventLog 9.0.8
- System.Diagnostics.PerformanceCounter 9.0.8
- System.Drawing.Common 9.0.8
- System.IO.Abstractions 22.0.13
- System.IO.Abstractions.TestingHelpers 22.0.13
- System.Management 9.0.7
- System.Management 9.0.8
- System.Net.Http 4.3.4
- System.Private.Uri 4.3.2
- System.Reactive 6.0.1
- System.Runtime.Caching 9.0.7
- System.ServiceProcess.ServiceController 9.0.7
- System.Text.Encoding.CodePages 9.0.7
- System.Text.Json 9.0.7
- System.Runtime.Caching 9.0.8
- System.ServiceProcess.ServiceController 9.0.8
- System.Text.Encoding.CodePages 9.0.8
- System.Text.Json 9.0.8
- System.Text.RegularExpressions 4.3.1
- UnicodeInformation 2.6.0
- UnitsNet 5.56.0

View File

@@ -262,6 +262,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "utils", "utils", "{B39DC643
src\common\utils\EventLocker.h = src\common\utils\EventLocker.h
src\common\utils\EventWaiter.h = src\common\utils\EventWaiter.h
src\common\utils\excluded_apps.h = src\common\utils\excluded_apps.h
src\common\utils\shell_ext_registration.h = src\common\utils\shell_ext_registration.h
src\common\utils\exec.h = src\common\utils\exec.h
src\common\utils\game_mode.h = src\common\utils\game_mode.h
src\common\utils\gpo.h = src\common\utils\gpo.h
@@ -461,6 +462,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Peek.Common", "src\modules\
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Peek.FilePreviewer", "src\modules\peek\Peek.FilePreviewer\Peek.FilePreviewer.csproj", "{AA9F0AF8-7924-4D59-BAA1-E36F1304E0DC}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Peek.UITests", "src\modules\peek\Peek.UITests\Peek.UITests.csproj", "{4E0AE3A4-2EE0-44D7-A2D0-8769977254A5}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "MarkdownPreviewHandlerCpp", "src\modules\previewpane\MarkdownPreviewHandlerCpp\MarkdownPreviewHandlerCpp.vcxproj", "{ED9A1AC6-AEB0-4569-A6E9-E1696182B545}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "GcodePreviewHandlerCpp", "src\modules\previewpane\GcodePreviewHandlerCpp\GcodePreviewHandlerCpp.vcxproj", "{5A5DD09D-723A-44D3-8F2B-293584C3D731}"
@@ -784,6 +787,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.TimeDa
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.WindowWalker.UnitTests", "src\modules\cmdpal\Tests\Microsoft.CmdPal.Ext.WindowWalker.UnitTests\Microsoft.CmdPal.Ext.WindowWalker.UnitTests.csproj", "{E816D7B0-4688-4ECB-97CC-3D8E798F3829}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.UnitTestBase", "src\modules\cmdpal\Tests\Microsoft.CmdPal.Ext.UnitTestsBase\Microsoft.CmdPal.Ext.UnitTestBase.csproj", "{00D8659C-2068-40B6-8B86-759CD6284BBB}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|ARM64 = Debug|ARM64
@@ -1852,6 +1857,14 @@ Global
{AA9F0AF8-7924-4D59-BAA1-E36F1304E0DC}.Release|ARM64.Build.0 = Release|ARM64
{AA9F0AF8-7924-4D59-BAA1-E36F1304E0DC}.Release|x64.ActiveCfg = Release|x64
{AA9F0AF8-7924-4D59-BAA1-E36F1304E0DC}.Release|x64.Build.0 = Release|x64
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A5}.Debug|ARM64.ActiveCfg = Debug|ARM64
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A5}.Debug|ARM64.Build.0 = Debug|ARM64
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A5}.Debug|x64.ActiveCfg = Debug|x64
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A5}.Debug|x64.Build.0 = Debug|x64
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A5}.Release|ARM64.ActiveCfg = Release|ARM64
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A5}.Release|ARM64.Build.0 = Release|ARM64
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A5}.Release|x64.ActiveCfg = Release|x64
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A5}.Release|x64.Build.0 = Release|x64
{ED9A1AC6-AEB0-4569-A6E9-E1696182B545}.Debug|ARM64.ActiveCfg = Debug|ARM64
{ED9A1AC6-AEB0-4569-A6E9-E1696182B545}.Debug|ARM64.Build.0 = Debug|ARM64
{ED9A1AC6-AEB0-4569-A6E9-E1696182B545}.Debug|x64.ActiveCfg = Debug|x64
@@ -2830,6 +2843,14 @@ Global
{E816D7B0-4688-4ECB-97CC-3D8E798F3829}.Release|ARM64.Build.0 = Release|ARM64
{E816D7B0-4688-4ECB-97CC-3D8E798F3829}.Release|x64.ActiveCfg = Release|x64
{E816D7B0-4688-4ECB-97CC-3D8E798F3829}.Release|x64.Build.0 = Release|x64
{00D8659C-2068-40B6-8B86-759CD6284BBB}.Debug|ARM64.ActiveCfg = Debug|ARM64
{00D8659C-2068-40B6-8B86-759CD6284BBB}.Debug|ARM64.Build.0 = Debug|ARM64
{00D8659C-2068-40B6-8B86-759CD6284BBB}.Debug|x64.ActiveCfg = Debug|x64
{00D8659C-2068-40B6-8B86-759CD6284BBB}.Debug|x64.Build.0 = Debug|x64
{00D8659C-2068-40B6-8B86-759CD6284BBB}.Release|ARM64.ActiveCfg = Release|ARM64
{00D8659C-2068-40B6-8B86-759CD6284BBB}.Release|ARM64.Build.0 = Release|ARM64
{00D8659C-2068-40B6-8B86-759CD6284BBB}.Release|x64.ActiveCfg = Release|x64
{00D8659C-2068-40B6-8B86-759CD6284BBB}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -2988,6 +3009,7 @@ Global
{9D7A6DE0-7D27-424D-ABAE-41B2161F9A03} = {17B4FA70-001E-4D33-BBBB-0D142DBC2E20}
{17A99C7C-0BFF-45BB-A9FD-63A0DDC105BB} = {17B4FA70-001E-4D33-BBBB-0D142DBC2E20}
{AA9F0AF8-7924-4D59-BAA1-E36F1304E0DC} = {17B4FA70-001E-4D33-BBBB-0D142DBC2E20}
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A5} = {17B4FA70-001E-4D33-BBBB-0D142DBC2E20}
{ED9A1AC6-AEB0-4569-A6E9-E1696182B545} = {2F305555-C296-497E-AC20-5FA1B237996A}
{5A5DD09D-723A-44D3-8F2B-293584C3D731} = {2F305555-C296-497E-AC20-5FA1B237996A}
{B3E869C4-8210-4EBD-A621-FF4C4AFCBFA9} = {2F305555-C296-497E-AC20-5FA1B237996A}
@@ -3117,11 +3139,6 @@ Global
{D9BD324E-1D80-44AA-8E7B-73EB00944434} = {38BDB927-829B-4C65-9CD9-93FB05D66D65}
{8EF25507-2575-4ADE-BF7E-D23376903AB8} = {3846508C-77EB-4034-A702-F8BB263C4F79}
{070AC093-C9F2-20AD-0BCD-F318FC2761EA} = {B1234567-1234-1234-1234-123456789ABC}
{E816D7AC-4688-4ECB-97CC-3D8E798F3825} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
{E816D7AD-4688-4ECB-97CC-3D8E798F3826} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
{E816D7AE-4688-4ECB-97CC-3D8E798F3827} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
{E816D7AF-4688-4ECB-97CC-3D8E798F3828} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
{E816D7B0-4688-4ECB-97CC-3D8E798F3829} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
{2C318EC3-BA86-4372-B1BC-DB0F33C208B2} = {322566EF-20DC-43A6-B9F8-616AF942579A}
{BFFB607F-7C78-434B-86B9-DA4C8196A1B5} = {B6C42F16-73EB-477E-8B0D-4E6CF6C20AAC}
{66E1534A-1587-42B2-912F-45C994D32904} = {89E20BCE-EB9C-46C8-8B50-E01A82E6FDC3}
@@ -3139,6 +3156,12 @@ Global
{806BF185-8B89-5BE1-9AA1-DA5BC9487DB9} = {264B412F-DB8B-4CF8-A74B-96998B183045}
{F93C2817-C846-4259-84D8-B39A6B57C8DE} = {3527BF37-DFC5-4309-A032-29278CA21328}
{8131151D-B0E9-4E18-84A5-E5F946C4480A} = {929C1324-22E8-4412-A9A8-80E85F3985A5}
{E816D7AC-4688-4ECB-97CC-3D8E798F3825} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
{E816D7AD-4688-4ECB-97CC-3D8E798F3826} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
{E816D7AE-4688-4ECB-97CC-3D8E798F3827} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
{E816D7AF-4688-4ECB-97CC-3D8E798F3828} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
{E816D7B0-4688-4ECB-97CC-3D8E798F3829} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
{00D8659C-2068-40B6-8B86-759CD6284BBB} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0}

202
README.md
View File

@@ -14,7 +14,7 @@ Microsoft PowerToys is a set of utilities for power users to tune and streamline
| [Color Picker](https://aka.ms/PowerToysOverview_ColorPicker) | [Command Not Found](https://aka.ms/PowerToysOverview_CmdNotFound) | [Command Palette](https://aka.ms/PowerToysOverview_CmdPal) |
| [Crop And Lock](https://aka.ms/PowerToysOverview_CropAndLock) | [Environment Variables](https://aka.ms/PowerToysOverview_EnvironmentVariables) | [FancyZones](https://aka.ms/PowerToysOverview_FancyZones) |
| [File Explorer Add-ons](https://aka.ms/PowerToysOverview_FileExplorerAddOns) | [File Locksmith](https://aka.ms/PowerToysOverview_FileLocksmith) | [Hosts File Editor](https://aka.ms/PowerToysOverview_HostsFileEditor) |
| [Image Resizer](https://aka.ms/PowerToysOverview_ImageResizer) | [Keyboard Manager](https://aka.ms/PowerToysOverview_KeyboardManager) | [Mouse utilities](https://aka.ms/PowerToysOverview_MouseUtilities) |
| [Image Resizer](https://aka.ms/PowerToysOverview_ImageResizer) | [Keyboard Manager](https://aka.ms/PowerToysOverview_KeyboardManager) | [Mouse Utilities](https://aka.ms/PowerToysOverview_MouseUtilities) |
| [Mouse Without Borders](https://aka.ms/PowerToysOverview_MouseWithoutBorders) | [New+](https://aka.ms/PowerToysOverview_NewPlus) | [Paste as Plain Text](https://aka.ms/PowerToysOverview_PastePlain) |
| [Peek](https://aka.ms/PowerToysOverview_Peek) | [PowerRename](https://aka.ms/PowerToysOverview_PowerRename) | [PowerToys Run](https://aka.ms/PowerToysOverview_PowerToysRun) |
| [Quick Accent](https://aka.ms/PowerToysOverview_QuickAccent) | [Registry Preview](https://aka.ms/PowerToysOverview_RegistryPreview) | [Screen Ruler](https://aka.ms/PowerToysOverview_ScreenRuler) |
@@ -35,19 +35,19 @@ Microsoft PowerToys is a set of utilities for power users to tune and streamline
Go to the [Microsoft PowerToys GitHub releases page][github-release-link] and click on `Assets` at the bottom to show the files available in the release. Please use the appropriate PowerToys installer that matches your machine's architecture and install scope. For most, it is `x64` and per-user.
<!-- 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.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
[github-next-release-work]: https://github.com/microsoft/PowerToys/issues?q=is%3Aissue+milestone%3A%22PowerToys+0.94%22
[github-current-release-work]: https://github.com/microsoft/PowerToys/issues?q=is%3Aissue+milestone%3A%22PowerToys+0.93%22
[ptUserX64]: https://github.com/microsoft/PowerToys/releases/download/v0.93.0/PowerToysUserSetup-0.93.0-x64.exe
[ptUserArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.93.0/PowerToysUserSetup-0.93.0-arm64.exe
[ptMachineX64]: https://github.com/microsoft/PowerToys/releases/download/v0.93.0/PowerToysSetup-0.93.0-x64.exe
[ptMachineArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.93.0/PowerToysSetup-0.93.0-arm64.exe
| Description | Filename |
|----------------|----------|
| 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] |
| Per user - x64 | [PowerToysUserSetup-0.93.0-x64.exe][ptUserX64] |
| Per user - ARM64 | [PowerToysUserSetup-0.93.0-arm64.exe][ptUserArm64] |
| Machine wide - x64 | [PowerToysSetup-0.93.0-x64.exe][ptMachineX64] |
| Machine wide - ARM64 | [PowerToysSetup-0.93.0-arm64.exe][ptMachineArm64] |
This is our preferred method.
@@ -93,139 +93,119 @@ For guidance on developing for PowerToys, please read the [developer docs](./doc
Our [prioritized roadmap][roadmap] of features and utilities that the core team is focusing on.
### 0.92 - June 2025 Update
### 0.93 - Aug 2025 Update
In this release, we focused on new features, stability, optimization improvements, and automation.
**✨Highlights**
- PowerToys settings now has a toggle for the system tray icon, giving users control over its visibility based on personal preference. Thanks [@BLM16](https://github.com/BLM16)!
- Command Palette now has Ahead-of-Time ([AOT](https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot)) compatibility for all first-party extensions, improved extensibility, and core UX fixes, resulting in better performance and stability across commands.
- Color Picker now has customizable mouse button actions, enabling more personalized workflows by assigning functions to left, right, and middle clicks. Thanks [@PesBandi](https://github.com/PesBandi)!
- Bug Report Tool now has a faster and clearer reporting process, with progress indicators, improved compression, auto-cleanup of old trace logs, and inclusion of MSIX installer logs for more efficient diagnostics.
- File Explorer add-ons now have improved rendering stability, resolving issues with PDF previews, blank thumbnails, and text file crashes during file browsing.
### Color Picker
- Added mouse button actions so you can choose what left, right, or middle click does. Thanks [@PesBandi](https://github.com/PesBandi)!
### Crop & Lock
- Aligned window styling with current Windows theme for a cleaner look. Thanks [@sadirano](https://github.com/sadirano)!
- PowerToys settings debuts a modern, card-based dashboard with clearer descriptions and faster navigation for a streamlined user experience.
- Command Palette had over 99 issues resolved, including bringing back Clipboard History, adding context menu shortcuts, pinning favorite apps, and supporting history in Run.
- Command Palette reduced its startup memory usage by ~15%, load time by ~40%, built-in extensions loading time by ~70%, and installation size by ~55%—all due to using the full Ahead-of-Time (AOT) compilation mode in Windows App SDK.
- Peek now supports instant previews and embedded thumbnails for Binary G-code (.bgcode) 3D printing files, making it easy to inspect models at a glance. Thanks [@pedrolamas](https://github.com/pedrolamas)!
- Mouse Utilities introduces a new spotlight highlighting mode that dims the screen and draws attention to your cursor, perfect for presentations.
- Test coverage improvements for multiple PowerToys modules including Command Palette, Advanced Paste, Peek, Text Extractor, and PowerRename — ensuring better reliability and quality, with over 600 new unit tests (mostly for Command Palette) and doubled UI automation coverage.
### Command Palette
- Enhanced performance by resolving a regression in page loading.
- Applied consistent hotkey handling across all Command Palette commands for a smoother user experience.
- Improved graceful closing of Command Palette. Thanks [@davidegiacometti](https://github.com/davidegiacometti)!
- Fixed consistency issue for extensions' alias with "Direct" setting and enabled localization for "Direct" and "Indirect" for better user understanding. Thanks [@davidegiacometti](https://github.com/davidegiacometti)!
- Improved visual clarity by styling critical context items correctly.
- Automatically focused the field when only one is present on the content page.
- Improved stability and efficiency when loading file icons in SDK ThumbnailHelper.cs by removing unnecessary operations. Thanks [@OldUser101](https://github.com/OldUser101)!
- Enhanced details view with commands implementation. (See [Extension sample](./src/modules/cmdpal/ext/SamplePagesExtension/Pages/SampleListPageWithDetails.cs))
- Ensured screen readers are notified when the selected item in the list changes for better accessibility.
- Fixed command title changes not being properly notified to screen readers. Thanks [@jiripolasek](https://github.com/jiripolasek)!
- Made icon controls excluded from keyboard navigation by default for better accessibility. Thanks [@jiripolasek](https://github.com/jiripolasek)!
- Improved UI design with better text sizing and alignment.
- Fixed keyboard shortcuts to work better in text boxes and context menus.
- Added right-click context menus with critical command styling and separators.
- Improved various context menu issues, improving item selection, handling of long titles, search bar text scaling, initial item behavior, and primary button functionality.
- Fixed context menu crashes with better type handling.
- Fixed "Reload" command to work with both uppercase and lowercase letters.
- Added mouse back button support for easier navigation. Thanks [@jiripolasek](https://github.com/jiripolasek)!
- Fixed Alt+Left Arrow navigation not working when search box contains text. Thanks [@jiripolasek](https://github.com/jiripolasek)!
- Updated back button tooltip to show keyboard shortcut information. Thanks [@jiripolasek](https://github.com/jiripolasek)!
- Fixed Command Palette window not appearing properly when activated. Thanks [@jiripolasek](https://github.com/jiripolasek)!
- Fixed Command Palette window staying hidden from taskbar after File Explorer restarts. Thanks [@jiripolasek](https://github.com/jiripolasek)!
- Fixed window focus not returning to previous app properly.
- Fixed Command Palette window to always appear on top when shown and move to bottom when hidden. Thanks [@jiripolasek](https://github.com/jiripolasek)!
- Fixed window hiding to properly work on UI thread. Thanks [@jiripolasek](https://github.com/jiripolasek)!
- Fixed crashes and improved stability with better synchronization of Command list updates. Thanks [@jiripolasek](https://github.com/jiripolasek)!
- Improved extension disposal with better error handling to prevent crashes. Thanks [@jiripolasek](https://github.com/jiripolasek)!
- Improved stability by fixing a UI threading issue when loading more results, preventing possible crashes and ensuring the loading state resets if loading fails. Thanks [@jiripolasek](https://github.com/jiripolasek)!
- Enhanced icon loading stability with better exception handling. Thanks [@jiripolasek](https://github.com/jiripolasek)!
- Added thread safety to recent commands to prevent crashes. Thanks [@MaoShengelia](https://github.com/MaoShengelia)!
- Fixed acrylic (frosted glass) system backdrop display issues by ensuring proper UI thread handling. Thanks [@jiripolasek](https://github.com/jiripolasek)!
### Command Palette extensions
- Added "Copy Path" command to *App* search results for convenience. Thanks [@PesBandi](https://github.com/PesBandi)!
- Improved *Calculator* input experience by ignoring leading equal signs. Thanks [@PesBandi](https://github.com/PesBandi)!
- Corrected input handling in the *Calculator* extension to avoid showing errors for input with only leading whitespace.
- Improved *New Extension* wizard by validating names to prevent namespace errors.
- Ensured consistent context items display for the *Run* extension between fallback and top-level results.
- Fixed missing *Time & Date* commands in fallback results. Thanks [@htcfreek](https://github.com/htcfreek)!
- Fixed outdated results in the *Time & Date* extension. Thanks [@htcfreek](https://github.com/htcfreek)!
- Fixed an issue where *Web Search* always opened Microsoft Edge instead of the user's default browser on Windows 11 24H2 and later. Thanks [@RuggMatt](https://github.com/RuggMatt)!
- Improved ordering of *Windows Settings* extension search results from alphabetical to relevance-based for quicker access.
- Added "Restart Windows Explorer" command to the *Windows System Commands* provider for gracefully terminate and relaunch explorer.exe. Thanks [@jiripolasek](https://github.com/jiripolasek)!
- Added settings to each provider to control which fallback commands are enabled. Thanks [@jiripolasek](https://github.com/jiripolasek)! for fixing a regression in this feature.
- Added sample code showing how Command Palette extensions can track when their pages are loaded or unloaded. [Check it out here](./src/modules/cmdpal/ext/SamplePagesExtension/OnLoadPage.cs).
- Fixed *Calculator* to accept regular spaces in numbers that use space separators. Thanks [@PesBandi](https://github.com/PesBandi)!
- Added a new setting to *Calculator* to make "Copy" the primary button (replacing “Save”) and enable "Close on Enter", streamlining the workflow. Thanks [@PesBandi](https://github.com/PesBandi)!
- Improved *Apps* indexing error handling and removed obsolete code. Thanks [@davidegiacometti](https://github.com/davidegiacometti)!
- Prevented apps from showing in search when the *Apps* extension is disabled. Thanks [@jiripolasek](https://github.com/jiripolasek)!
- Added ability to pin/unpin *Apps* using Ctrl+P shortcut.
- Added keyboard shortcuts to the *Apps* context menu items for faster access.
- Added all file context menu options to the *Apps* items context menu, making all file actions available there for better functionality.
- Streamlined All *Apps* extension settings by removing redundant descriptions, making the UI clearer.
- Added command history to the *Run* page for easier access to previous commands.
- Fixed directory path handling in *Run* fallback for better file navigation.
- Fixed URL fallback item hiding properly in *Web Search* extension when search query becomes invalid. Thanks [@jiripolasek](https://github.com/jiripolasek)!
- Added proper empty state message for *Web Search* extension when no results found. Thanks [@jiripolasek](https://github.com/jiripolasek)!
- Added fallback command to *Windows Settings* extension for better search results.
- Re-enabled *Clipboard History* feature with proper window handling.
- Improved *Add Bookmark* extension to automatically detect file, folder, or URL types without manual input.
- Updated terminology from "Kill process" to "End task" in *Window Walker* for consistency with Windows.
- Fixed minor grammar error in SamplePagesExtension code comments. Thanks [@purofle](https://github.com/purofle)!
### Command Palette Ahead-of-Time (AOT) readiness
### Mouse Utilities
- Weve made foundational changes to prepare the Command Palette for future Ahead-of-Time (AOT) publishing. This includes replacing the calculator library with ExprTk, improving COM object handling, refining Win32 interop, and correcting trimming behavior—all to ensure compatibility, performance, and reliability under AOT constraints. All first-party extensions are now AOT-compatible. These improvements lay the groundwork for publishing Command Palette as an AOT application in the next release.
- Special thanks to [@Sergio0694](https://github.com/Sergio0694) for guidance on making COM APIs AOT-compatible, [@jtschuster](https://github.com/jtschuster) for fixing COM object handling, [@ArashPartow](https://github.com/ArashPartow) from ExprTk for integration suggestions, and [@tian-lt](https://github.com/tian-lt) from the Windows Calculator team for valuable suggestion throughout the migration journey and review.
- As part of the upcoming release, were also enabling AOT compatibility for key dependencies, including markdown rendering, Adaptive Cards, internal logging and telemetry library, and the core Command Palette UX.
### FancyZones
- Fixed DPI-scaling issues to ensure FancyZones Editor displays crisply on high-resolution monitors. Thanks [@HO-COOH](https://github.com/HO-COOH)! This inspired us a broader review across other PowerToys modules, leading to DPI display optimizations in Awake, Color Picker, PowerAccent, and more.
### File Explorer add-ons
- Fixed potential failures in PDF previewer and thumbnail generation, improving reliability when browsing PDF files. Thanks [@mohiuddin-khan-shiam](https://github.com/mohiuddin-khan-shiam)!
- Prevented Monaco Preview Handler crash when opening UTF-8-BOM text files.
### Hosts File Editor
- Added an in-app *“Learn more”* link to warning dialogs for quick guidance. Thanks [@PesBandi](https://github.com/PesBandi)!
### Mouse Without Borders
- Fixed firewall rule so MWB now accepts connections from IPs outside your local subnet.
- Cleaned legacy logs to reduce disk usage and noise.
- Added a new spotlight highlighting mode that creates a large transparent circle around your cursor with a backdrop effect, providing an alternative to the traditional circle highlight. Perfect for presentations where you want to focus attention on a specific area while dimming the rest of the screen.
### Peek
- Updated QOI reader so 3-channel QOI images preview correctly in Peek and File Explorer. Thanks [@mbartlett21](https://github.com/mbartlett21)!
- Added codec detection with a clear warning when a video cant be previewed, along with a link to the Microsoft Store to download the required codec.
- Added preview and thumbnail support for Binary G-code (.bgcode) files used in 3D printing. You can now see embedded thumbnails and preview these compressed 3D printing files directly in Peek and File Explorer. Thanks [@pedrolamas](https://github.com/pedrolamas)!
### PowerRename
### Quick Accent
- Added support for $YY-$MM-$DD in ModificationTime and AccessTime to enable flexible date-based renaming.
### PowerToys Run
- Suppressed error UI for known WPF-related crashes to reduce user confusion, while retaining diagnostic logging for analysis. This targets COMException 0xD0000701 and 0x80263001 caused by temporary DWM unavailability.
### Registry Preview
- Added "Extended data preview" via magnifier icon and context menu in the Data Grid, enabled easier inspection of complex registry types like REG_BINARY, REG_EXPAND_SZ, and REG_MULTI_SZ, etc. Thanks [@htcfreek](https://github.com/htcfreek)!
- Improved file-saving experience in Registry Preview by aligning with Notepad-like behavior, enhancing user prompts, error handling, and preventing crashes during unsaved or interrupted actions. Thanks [@htcfreek](https://github.com/htcfreek)!
- Added Vietnamese language support to Quick Accent, mappings for Vietnamese vowels (a, e, i, o, u, y) and the letter d. Thanks [@octastylos-pseudodipteros](https://github.com/octastylos-pseudodipteros)!
### Settings
- Added an option to hide or show the PowerToys system tray icon. Thanks [@BLM16](https://github.com/BLM16)!
- Improved settings to show progress while a bug report package is being generated.
### Workspaces
- Stored Workspaces icons in user AppData to ensure profile portability and prevent loss during temporary folder cleanup.
- Enabled capture and launch of PWAs on non-default Edge or Chrome profiles, ensuring consistent behavior during creation and execution.
- Completely redesigned the Settings dashboard with a modern card-based layout featuring organized sections for quick actions and shortcuts overview, replacing the old module list.
- Rewrote setting descriptions to be more concise and follow Windows writing style guidelines, making them easier to understand.
- Improved formatting and readability of release notes in the "What's New" section with better typography and spacing.
- Added missing deep link support for various settings pages (Peek, Quick Accent, PowerToys Run, etc.) so you can jump directly to specific settings.
- Resolved an issue where the settings page header would drift away from its position when resizing the settings window.
- Resolved a settings crash related to incompatible property names in ZoomIt configuration.
### Documentation
- Added SpeedTest and Dictionary Definition to the third-party plugins documentation for PowerToys Run. Thanks [@ruslanlap](https://github.com/ruslanlap)!
- Corrected sample links and typo in Command Palette documentation. Thanks [@daverayment](https://github.com/daverayment) and [@roycewilliams](https://github.com/roycewilliams)!
- Added detailed step-by-step instructions for first-time developers building the Command Palette module, including prerequisites and Visual Studio setup guidance. Thanks [@chatasweetie](https://github.com/chatasweetie)!
- **Fixed Broken SDK Link**: Corrected a broken markdown link in the Command Palette SDK README that was pointing to an incorrect directory path. Thanks [@ChrisGuzak](https://github.com/ChrisGuzak)!
- Added documentation for the "Open With Cursor" plugin that enables opening Visual Studio and VS Code recent files using Cursor AI. Thanks [@VictorNoxx](https://github.com/VictorNoxx)!
- Added documentation for two new community plugins - Hotkeys plugin for creating custom keyboard shortcuts, and RandomGen plugin for generating random data like passwords, colors, and placeholder text. Thanks [@ruslanlap](https://github.com/ruslanlap)!
### Development
- Updated .NET libraries to 9.0.6 for performance and security. Thanks [@snickler](https://github.com/snickler)!
- Updated WinAppSDK to 1.7.2 for better stability and Windows support.
- Introduced a one-step local build script that generates a signed installer, enhancing developer productivity.
- Generated portable PDBs so cross-platform debuggers can read symbol files, improving debugging experience in VSCode and other tools.
- Simplified WinGet configuration files by using the [Microsoft.Windows.Settings](https://www.powershellgallery.com/packages/Microsoft.Windows.Settings) module to enable Developer Mode. Thanks [@mdanish-kh](https://github.com/mdanish-kh)!
- Adjusted build scripts for the latest Az.Accounts module to keep CI green.
- Streamlined release pipeline by removing hard-coded telemetry version numbers, and unified Command Palette versioning with Windows Terminal's versioning method for consistent updates.
- Enhanced the build validation step to show detailed differences between NOTICE.md and actual package dependencies and versions.
- Improved spell-checking accuracy across the repo. Thanks [@rovercoder](https://github.com/rovercoder)!
- Upgraded CI to TouchdownBuild v5 for faster pipelines.
- Added context comments to *Resources.resw* to help translators.
- Expanded fuzz testing coverage to include FancyZones.
- Integrated all unit tests into the CI pipeline, increasing from ~3,000 to ~5,000 tests.
- Enabled daily UI test automation on the main branch, now covering over 370 UI tests for end-to-end validation.
- Newly added unit tests for WorkspacesLib to improve reliability and maintainability.
- Updated .NET libraries to 9.0.8 for performance and security. Thanks [@snickler](https://github.com/snickler)!
- Updated the spell check system to version 0.0.25 with better GitHub integration and SARIF reporting, plus fixed numerous spelling errors throughout the codebase including property names and documentation. Thanks [@jsoref](https://github.com/jsoref)!
- Cleaned up spelling check configuration to eliminate false positives and excessive noise that was appearing in every pull request, making the development process smoother.
- Replaced NuGet feed with Azure Artifacts for better package management.
- Implemented configurable UI test pipeline that can use pre-built official releases instead of building everything from scratch, reducing test execution time from 2+ hours.
- Replaced brittle pixel-by-pixel image comparison with perceptual hash (pHash) technology that's more robust to minor rendering differences - no more test failures due to anti-aliasing or compression artifacts.
- Reduced CI/fuzzing/UI test timeouts from 4 hours to 90 minutes, dramatically improving developer feedback loops and preventing long waits when builds get stuck.
- Standardized test project naming across the entire codebase and improved pipeline result identification by adding platform/install mode context to test run titles. Thanks [@khmyznikov](https://github.com/khmyznikov)!
- Added comprehensive UI test suites for multiple PowerToys modules including Command Palette, Advanced Paste, Peek, Text Extractor, and PowerRename - ensuring better reliability and quality.
- Enhanced UI test automation with command-line argument support, better session management, and improved element location methods using pattern matching to avoid failures from minor differences in exact matches.
### General
### What is being planned over the next few releases
- Updated bug report compression library (cziplib 0.3.3) for faster and more reliable package creation. Thanks [@Chubercik](https://github.com/Chubercik)!
- Included App Installer (“AppX Deployment Server”) event logs in bug reports for more thorough diagnostics.
### What is being planned for version 0.93
For [v0.93][github-next-release-work], we'll work on the items below:
For [v0.94][github-next-release-work], we'll work on the items below:
- Continued Command Palette polish
- New UI automation tests
- Working on installer upgrades
- Working on Shortcut Guide v2 (Thanks [@noraa-junker](https://github.com/noraa-junker)!)
- Working on upgrading the installer to WiX 5
- Working on shortcut conflict detection
- Working on setting search
- Upgrading Keyboard Manager's editor UI
- New UI automation tests
- Stability, bug fixes
## PowerToys Community

View File

@@ -22,23 +22,23 @@ The PowerToys UI test pipeline provides flexible options for building and testin
### 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.
- **buildSource**: Select the build type for testing:
- `latestMainOfficialBuild`: Downloads and uses the latest official PowerToys build from main branch
- `buildNow`: Builds PowerToys from current source code and uses it for testing
- `specificBuildId`: Downloads a specific PowerToys build using the build ID specified in `specificBuildId` parameter
- **useCurrentBranchBuild**: When checked along with `useLatestOfficialBuild`, downloads the official build from the current branch instead of main.
**Default value**: `latestMainOfficialBuild`
**Default value**: `false` (downloads from main branch)
- **specificBuildId**: When `buildSource` is set to `specificBuildId`, specify the exact PowerToys build ID to download and test against.
**Default value**: `"xxxx"` (placeholder, enter actual build ID when using specificBuildId option)
**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
- Testing against a specific known build for reproducibility
- Regression testing against a particular build version
- Validating fixes in a specific build before release
**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
**Usage**: Enter the build ID number (e.g., `12345`) to download that specific build. Only used when `buildSource` is set to `specificBuildId`.
- **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
@@ -50,19 +50,19 @@ The PowerToys UI test pipeline provides flexible options for building and testin
### 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
1. **Official Build Testing** (`buildSource = latestMainOfficialBuild` or `specificBuildId`)
- Downloads and installs official PowerToys build (latest from main or specific build ID)
- Builds only UI test projects (all or specific based on `uiTestModules`)
- Runs UI tests against installed PowerToys
- Tests both machine-level and per-user installation modes automatically
2. **Full Build + Testing** (`useLatestOfficialBuild = false`)
- Builds entire PowerToys solution
2. **Current Source Build Testing** (`buildSource = buildNow`)
- Builds entire PowerToys solution from current source code
- 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
- Runs UI tests against freshly built PowerToys
- Uses artifacts from current pipeline build
> **Note**: Both modes support the `uiTestModules` parameter to control which specific UI test modules to build and run.
> **Note**: All modes support the `uiTestModules` parameter to control which specific UI test modules to build and run. Both machine-level and per-user installation modes are tested automatically when using official builds.
### Pipeline Access
- Pipeline: https://microsoft.visualstudio.com/Dart/_build?definitionId=161438&_a=summary

View File

@@ -87,6 +87,13 @@
### Building PowerToys Locally
#### One stop script for building installer
1. Open developer powershell for vs 2022
2. Run tools\build\build-installer.ps1
> For the first-time setup, please run the installer as an administrator. This ensures that the Wix tool can move wix.target to the desired location and trust the certificate used to sign the MSIX packages.
The following manual steps will not install the MSIX apps (such as Command Palette) on your local installer.
#### Prerequisites for building the MSI installer
1. Install the [WiX Toolset Visual Studio 2022 Extension](https://marketplace.visualstudio.com/items?itemName=WixToolset.WixToolsetVisualStudio2022Extension).

View File

@@ -0,0 +1,33 @@
## If for any reason, you'd like to test winget install scenario, you can follow this doc:
### Powertoys winget manifest definition:
[winget repository](https://github.com/microsoft/winget-pkgs/tree/master/manifests/m/Microsoft/PowerToys)
### How to test a winget installation locally:
1. Get artifacts from release CI pipeline Pipelines - Runs for PowerToys Signed YAML Release Build, or you can build one yourself by execute the
'tools\build\build-installer.ps1' script
2. Get the artifact hash, this is required to define winget manifest
```powershell
cd /path/to/your/directory/contains/installer
Get-FileHash -Path ".\<Installer-name>.exe" -Algorithm SHA256
```
3. Host your installer.exe - Attention: staged github release artifacts or artifacts in release pipeline is not OK in this step
You can self-host it or you can upload to a publicly available endpoint
**How to selfhost it** (A extremely simple way):
```powershell
python -m http.server 8000
```
4. Download a version folder from wingetpkgs like: [version 0.92.1](https://github.com/microsoft/winget-pkgs/tree/master/manifests/m/Microsoft/PowerToys/0.92.1)
and you get **a folder contains 3 yml files**
>note: Do not put any files other than these three in this folder
5. Modify the yml files based on your version and the self hosted artifact link, and modify the sha256 hash for the installer you'd like to use
6. Start winget install:
```powershell
#execute as admin
winget settings --enable LocalManifestFiles
winget install --manifest "<folder_path_of_manifest_files>" --architecture x64 --scope user
```

View File

@@ -49,6 +49,7 @@ Contact the developers of a plugin directly for assistance with a specific plugi
| [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. |
| [Open With Cursor](https://github.com/VictorNoxx/PowerToys-Run-Cursor/) | [VictorNoxx](https://github.com/VictorNoxx) | Open Visual Studio, VS Code recents with Cursor AI |
## Extending software plugins

View File

@@ -14,21 +14,6 @@
<DirectoryRef Id="FileLocksmithAssetsInstallFolder" FileSource="$(var.FileLocksmithAssetsFilesPath)">
<!-- Generated by generateFileComponents.ps1 -->
<!--FileLocksmithAssetsFiles_Component_Def-->
<!-- !Warning! Make sure to change Component Guid if you update something here -->
<Component Id="Module_FileLocksmith" Guid="108D3EC1-E6E0-4E81-88EF-25966133CB41" Win64="yes">
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\CLSID\{84D68575-E186-46AD-B0CB-BAEB45EE29C0}">
<RegistryValue Type="string" Value="File Locksmith Shell Extension" />
<RegistryValue Type="string" Name="ContextMenuOptIn" Value="" />
<RegistryValue Type="string" Key="InprocServer32" Value="[WinUI3AppsInstallFolder]PowerToys.FileLocksmithExt.dll" />
<RegistryValue Type="string" Key="InprocServer32" Name="ThreadingModel" Value="Apartment" />
</RegistryKey>
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\AllFileSystemObjects\ShellEx\ContextMenuHandlers\FileLocksmithExt">
<RegistryValue Type="string" Value="{84D68575-E186-46AD-B0CB-BAEB45EE29C0}"/>
</RegistryKey>
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\Drive\ShellEx\ContextMenuHandlers\FileLocksmithExt">
<RegistryValue Type="string" Value="{84D68575-E186-46AD-B0CB-BAEB45EE29C0}"/>
</RegistryKey>
</Component>
</DirectoryRef>
<ComponentGroup Id="FileLocksmithComponentGroup">
@@ -38,7 +23,6 @@
</RegistryKey>
<RemoveFolder Id="RemoveFolderFileLocksmithAssetsFolder" Directory="FileLocksmithAssetsInstallFolder" On="uninstall"/>
</Component>
<ComponentRef Id="Module_FileLocksmith" />
</ComponentGroup>
</Fragment>

View File

@@ -16,71 +16,6 @@
<!-- Generated by generateFileComponents.ps1 -->
<!--ImageResizerAssetsFiles_Component_Def-->
<Component Id="Module_ImageResizer_Registry" Win64="yes">
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\CLSID\{51B4D7E5-7568-4234-B4BB-47FB3C016A69}\InprocServer32">
<RegistryValue Value="[WinUI3AppsInstallFolder]PowerToys.ImageResizerExt.dll" Type="string" />
<RegistryValue Name="ThreadingModel" Value="Apartment" Type="string" />
</RegistryKey>
<RegistryValue Root="$(var.RegistryScope)"
Key="SOFTWARE\Classes\Directory\ShellEx\DragDropHandlers\ImageResizer"
Value="{51B4D7E5-7568-4234-B4BB-47FB3C016A69}"
Type="string" />
<!-- Registry Keys for the context menu handler for each of the following image formats: bmp, dib, gif, jfif, jpe, jpeg, jpg, jxr, png, rle, tif, tiff, wdp -->
<RegistryValue Root="$(var.RegistryScope)"
Key="SOFTWARE\Classes\SystemFileAssociations\.bmp\ShellEx\ContextMenuHandlers\ImageResizer"
Value="{51B4D7E5-7568-4234-B4BB-47FB3C016A69}"
Type="string" />
<RegistryValue Root="$(var.RegistryScope)"
Key="SOFTWARE\Classes\SystemFileAssociations\.dib\ShellEx\ContextMenuHandlers\ImageResizer"
Value="{51B4D7E5-7568-4234-B4BB-47FB3C016A69}"
Type="string" />
<RegistryValue Root="$(var.RegistryScope)"
Key="SOFTWARE\Classes\SystemFileAssociations\.gif\ShellEx\ContextMenuHandlers\ImageResizer"
Value="{51B4D7E5-7568-4234-B4BB-47FB3C016A69}"
Type="string" />
<RegistryValue Root="$(var.RegistryScope)"
Key="SOFTWARE\Classes\SystemFileAssociations\.jfif\ShellEx\ContextMenuHandlers\ImageResizer"
Value="{51B4D7E5-7568-4234-B4BB-47FB3C016A69}"
Type="string" />
<RegistryValue Root="$(var.RegistryScope)"
Key="SOFTWARE\Classes\SystemFileAssociations\.jpe\ShellEx\ContextMenuHandlers\ImageResizer"
Value="{51B4D7E5-7568-4234-B4BB-47FB3C016A69}"
Type="string" />
<RegistryValue Root="$(var.RegistryScope)"
Key="SOFTWARE\Classes\SystemFileAssociations\.jpeg\ShellEx\ContextMenuHandlers\ImageResizer"
Value="{51B4D7E5-7568-4234-B4BB-47FB3C016A69}"
Type="string" />
<RegistryValue Root="$(var.RegistryScope)"
Key="SOFTWARE\Classes\SystemFileAssociations\.jpg\ShellEx\ContextMenuHandlers\ImageResizer"
Value="{51B4D7E5-7568-4234-B4BB-47FB3C016A69}"
Type="string" />
<RegistryValue Root="$(var.RegistryScope)"
Key="SOFTWARE\Classes\SystemFileAssociations\.jxr\ShellEx\ContextMenuHandlers\ImageResizer"
Value="{51B4D7E5-7568-4234-B4BB-47FB3C016A69}"
Type="string" />
<RegistryValue Root="$(var.RegistryScope)"
Key="SOFTWARE\Classes\SystemFileAssociations\.png\ShellEx\ContextMenuHandlers\ImageResizer"
Value="{51B4D7E5-7568-4234-B4BB-47FB3C016A69}"
Type="string" />
<RegistryValue Root="$(var.RegistryScope)"
Key="SOFTWARE\Classes\SystemFileAssociations\.rle\ShellEx\ContextMenuHandlers\ImageResizer"
Value="{51B4D7E5-7568-4234-B4BB-47FB3C016A69}"
Type="string" />
<RegistryValue Root="$(var.RegistryScope)"
Key="SOFTWARE\Classes\SystemFileAssociations\.tif\ShellEx\ContextMenuHandlers\ImageResizer"
Value="{51B4D7E5-7568-4234-B4BB-47FB3C016A69}"
Type="string" />
<RegistryValue Root="$(var.RegistryScope)"
Key="SOFTWARE\Classes\SystemFileAssociations\.tiff\ShellEx\ContextMenuHandlers\ImageResizer"
Value="{51B4D7E5-7568-4234-B4BB-47FB3C016A69}"
Type="string" />
<RegistryValue Root="$(var.RegistryScope)"
Key="SOFTWARE\Classes\SystemFileAssociations\.wdp\ShellEx\ContextMenuHandlers\ImageResizer"
Value="{51B4D7E5-7568-4234-B4BB-47FB3C016A69}"
Type="string" />
</Component>
</DirectoryRef>
<ComponentGroup Id="ImageResizerComponentGroup">
@@ -90,7 +25,6 @@
</RegistryKey>
<RemoveFolder Id="RemoveFolderImageResizerAssetsFolder" Directory="ImageResizerAssetsFolder" On="uninstall"/>
</Component>
<ComponentRef Id="Module_ImageResizer_Registry" />
</ComponentGroup>
</Fragment>
</Wix>

View File

@@ -18,19 +18,6 @@
<DirectoryRef Id="NewPlusAssetsInstallFolder" FileSource="$(var.NewPlusAssetsFilesPath)">
<!-- Generated by generateFileComponents.ps1 -->
<!--NewPlusAssetsFiles_Component_Def-->
<!-- NewPlus Shell Extension for Win10 registration -->
<Component Id="NewPlus_ShellExtension_win10" Guid="D5456D4A-6EEC-4B85-944D-6A6A4A74FFA6" Win64="yes">
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\CLSID\{FF90D477-E32A-4BE8-8CC5-A502A97F5401}">
<RegistryValue Type="string" Value="NewPlus Shell Extension Win10" />
<RegistryValue Type="string" Name="ContextMenuOptIn" Value="" />
<RegistryValue Type="string" Key="InprocServer32" Value="[WinUI3AppsInstallFolder]PowerToys.NewPlus.ShellExtension.win10.dll" />
<RegistryValue Type="string" Key="InprocServer32" Name="ThreadingModel" Value="Apartment" />
</RegistryKey>
<RegistryKey Root="$(var.RegistryScope)" Key="SOFTWARE\Classes\Directory\background\ShellEx\ContextMenuHandlers\NewPlusShellExtensionWin10">
<RegistryValue Type="string" Value="{FF90D477-E32A-4BE8-8CC5-A502A97F5401}"/>
</RegistryKey>
</Component>
</DirectoryRef>
<ComponentGroup Id="NewPlusComponentGroup">
@@ -40,8 +27,7 @@
</RegistryKey>
<RemoveFolder Id="RemoveFolderNewPlusAssetsFolder" Directory="NewPlusAssetsInstallFolder" On="uninstall"/>
</Component>
<ComponentRef Id="NewPlus_ShellExtension_win10" />
</ComponentGroup>
</ComponentGroup>
<!-- Example templates -->
@@ -81,7 +67,7 @@
</Component>
<ComponentRef Id="NewPlusTemplateFiles_Component" />
<ComponentRef Id="NewPlusTemplateSubFiles_Component" />
</ComponentGroup>
</ComponentGroup>
</Fragment>
</Wix>

View File

@@ -14,22 +14,6 @@
<DirectoryRef Id="PowerRenameAssetsFolder" FileSource="$(var.PowerRenameAssetsFilesPath)">
<!-- Generated by generateFileComponents.ps1 -->
<!--PowerRenameAssetsFiles_Component_Def-->
<!-- !Warning! Make sure to change Component Guid if you update something here -->
<Component Id="Module_PowerRename" Guid="40D43079-240E-402D-8CE8-571BFFA71175" Win64="yes">
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\CLSID\{0440049F-D1DC-4E46-B27B-98393D79486B}">
<RegistryValue Type="string" Value="PowerRename Shell Extension" />
<RegistryValue Type="string" Name="ContextMenuOptIn" Value="" />
<RegistryValue Type="string" Key="InprocServer32" Value="[WinUI3AppsInstallFolder]PowerToys.PowerRenameExt.dll" />
<RegistryValue Type="string" Key="InprocServer32" Name="ThreadingModel" Value="Apartment" />
</RegistryKey>
<RegistryKey Root="$(var.RegistryScope)" Key="SOFTWARE\Classes\AllFileSystemObjects\ShellEx\ContextMenuHandlers\PowerRenameExt">
<RegistryValue Type="string" Value="{0440049F-D1DC-4E46-B27B-98393D79486B}"/>
</RegistryKey>
<RegistryKey Root="$(var.RegistryScope)" Key="SOFTWARE\Classes\Directory\background\ShellEx\ContextMenuHandlers\PowerRenameExt">
<RegistryValue Type="string" Value="{0440049F-D1DC-4E46-B27B-98393D79486B}"/>
</RegistryKey>
</Component>
</DirectoryRef>
<ComponentGroup Id="PowerRenameComponentGroup">
@@ -39,7 +23,6 @@
</RegistryKey>
<RemoveFolder Id="RemoveFolderPowerRenameAssetsFolder" Directory="PowerRenameAssetsFolder" On="uninstall"/>
</Component>
<ComponentRef Id="Module_PowerRename" />
</ComponentGroup>
</Fragment>

View File

@@ -176,6 +176,18 @@
<Custom Action="UnRegisterContextMenuPackages" Before="RemoveFiles">
Installed AND (REMOVE="ALL")
</Custom>
<Custom Action="CleanImageResizerRuntimeRegistry" Before="RemoveFiles">
Installed AND (REMOVE="ALL")
</Custom>
<Custom Action="CleanFileLocksmithRuntimeRegistry" Before="RemoveFiles">
Installed AND (REMOVE="ALL")
</Custom>
<Custom Action="CleanPowerRenameRuntimeRegistry" Before="RemoveFiles">
Installed AND (REMOVE="ALL")
</Custom>
<Custom Action="CleanNewPlusRuntimeRegistry" Before="RemoveFiles">
Installed AND (REMOVE="ALL")
</Custom>
<Custom Action="UnRegisterCmdPalPackage" Before="RemoveFiles">
Installed AND (NOT UPGRADINGPRODUCTCODE) AND (REMOVE="ALL")
</Custom>
@@ -437,6 +449,35 @@
Execute="deferred"
BinaryKey="PTCustomActions"
DllEntry="UnRegisterContextMenuPackagesCA"
/>
<CustomAction Id="CleanImageResizerRuntimeRegistry"
Return="ignore"
Impersonate="yes"
Execute="deferred"
BinaryKey="PTCustomActions"
DllEntry="CleanImageResizerRuntimeRegistryCA"
/>
<CustomAction Id="CleanFileLocksmithRuntimeRegistry"
Return="ignore"
Impersonate="yes"
Execute="deferred"
BinaryKey="PTCustomActions"
DllEntry="CleanFileLocksmithRuntimeRegistryCA"
/>
<CustomAction Id="CleanPowerRenameRuntimeRegistry"
Return="ignore"
Impersonate="yes"
Execute="deferred"
BinaryKey="PTCustomActions"
DllEntry="CleanPowerRenameRuntimeRegistryCA"
/>
<CustomAction Id="CleanNewPlusRuntimeRegistry"
Return="ignore"
Impersonate="yes"
Execute="deferred"
BinaryKey="PTCustomActions"
DllEntry="CleanNewPlusRuntimeRegistryCA"
/>
<CustomAction Id="UnRegisterCmdPalPackage"

View File

@@ -1153,6 +1153,113 @@ UINT __stdcall UnRegisterContextMenuPackagesCA(MSIHANDLE hInstall)
return WcaFinalize(er);
}
UINT __stdcall CleanImageResizerRuntimeRegistryCA(MSIHANDLE hInstall)
{
HRESULT hr = S_OK;
UINT er = ERROR_SUCCESS;
hr = WcaInitialize(hInstall, "CleanImageResizerRuntimeRegistryCA");
try
{
const wchar_t* CLSID_STR = L"{51B4D7E5-7568-4234-B4BB-47FB3C016A69}";
const wchar_t* exts[] = { L".bmp", L".dib", L".gif", L".jfif", L".jpe", L".jpeg", L".jpg", L".jxr", L".png", L".rle", L".tif", L".tiff", L".wdp" };
auto deleteKeyRecursive = [](HKEY root, const std::wstring &path) {
RegDeleteTreeW(root, path.c_str());
};
// InprocServer32 chain root CLSID
deleteKeyRecursive(HKEY_CURRENT_USER, L"Software\\Classes\\CLSID\\" + std::wstring(CLSID_STR));
// DragDrop handler
deleteKeyRecursive(HKEY_CURRENT_USER, L"Software\\Classes\\Directory\\ShellEx\\DragDropHandlers\\ImageResizer");
// Extensions
for (auto ext : exts)
{
deleteKeyRecursive(HKEY_CURRENT_USER, L"Software\\Classes\\SystemFileAssociations\\" + std::wstring(ext) + L"\\ShellEx\\ContextMenuHandlers\\ImageResizer");
}
// Sentinel
RegDeleteTreeW(HKEY_CURRENT_USER, L"Software\\Microsoft\\PowerToys\\ImageResizer");
}
catch (...)
{
er = ERROR_INSTALL_FAILURE;
}
er = er == ERROR_SUCCESS ? (SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE) : er;
return WcaFinalize(er);
}
UINT __stdcall CleanFileLocksmithRuntimeRegistryCA(MSIHANDLE hInstall)
{
HRESULT hr = S_OK;
UINT er = ERROR_SUCCESS;
hr = WcaInitialize(hInstall, "CleanFileLocksmithRuntimeRegistryCA");
try
{
const wchar_t* CLSID_STR = L"{84D68575-E186-46AD-B0CB-BAEB45EE29C0}";
auto deleteKeyRecursive = [](HKEY root, const std::wstring& path) {
RegDeleteTreeW(root, path.c_str());
};
deleteKeyRecursive(HKEY_CURRENT_USER, L"Software\\Classes\\CLSID\\" + std::wstring(CLSID_STR));
deleteKeyRecursive(HKEY_CURRENT_USER, L"Software\\Classes\\AllFileSystemObjects\\ShellEx\\ContextMenuHandlers\\FileLocksmithExt");
deleteKeyRecursive(HKEY_CURRENT_USER, L"Software\\Classes\\Drive\\ShellEx\\ContextMenuHandlers\\FileLocksmithExt");
RegDeleteTreeW(HKEY_CURRENT_USER, L"Software\\Microsoft\\PowerToys\\FileLocksmith");
}
catch (...)
{
er = ERROR_INSTALL_FAILURE;
}
er = er == ERROR_SUCCESS ? (SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE) : er;
return WcaFinalize(er);
}
UINT __stdcall CleanPowerRenameRuntimeRegistryCA(MSIHANDLE hInstall)
{
HRESULT hr = S_OK;
UINT er = ERROR_SUCCESS;
hr = WcaInitialize(hInstall, "CleanPowerRenameRuntimeRegistryCA");
try
{
const wchar_t* CLSID_STR = L"{0440049F-D1DC-4E46-B27B-98393D79486B}";
auto deleteKeyRecursive = [](HKEY root, const std::wstring& path) {
RegDeleteTreeW(root, path.c_str());
};
deleteKeyRecursive(HKEY_CURRENT_USER, L"Software\\Classes\\CLSID\\" + std::wstring(CLSID_STR));
deleteKeyRecursive(HKEY_CURRENT_USER, L"Software\\Classes\\AllFileSystemObjects\\ShellEx\\ContextMenuHandlers\\PowerRenameExt");
deleteKeyRecursive(HKEY_CURRENT_USER, L"Software\\Classes\\Directory\\background\\ShellEx\\ContextMenuHandlers\\PowerRenameExt");
RegDeleteTreeW(HKEY_CURRENT_USER, L"Software\\Microsoft\\PowerToys\\PowerRename");
}
catch (...)
{
er = ERROR_INSTALL_FAILURE;
}
er = er == ERROR_SUCCESS ? (SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE) : er;
return WcaFinalize(er);
}
UINT __stdcall CleanNewPlusRuntimeRegistryCA(MSIHANDLE hInstall)
{
HRESULT hr = S_OK;
UINT er = ERROR_SUCCESS;
hr = WcaInitialize(hInstall, "CleanNewPlusRuntimeRegistryCA");
try
{
const wchar_t* CLSID_STR = L"{FF90D477-E32A-4BE8-8CC5-A502A97F5401}";
auto deleteKeyRecursive = [](HKEY root, const std::wstring& path) {
RegDeleteTreeW(root, path.c_str());
};
deleteKeyRecursive(HKEY_CURRENT_USER, L"Software\\Classes\\CLSID\\" + std::wstring(CLSID_STR));
deleteKeyRecursive(HKEY_CURRENT_USER, L"Software\\Classes\\Directory\\background\\ShellEx\\ContextMenuHandlers\\NewPlusShellExtensionWin10");
RegDeleteTreeW(HKEY_CURRENT_USER, L"Software\\Microsoft\\PowerToys\\NewPlus");
}
catch (...)
{
er = ERROR_INSTALL_FAILURE;
}
er = er == ERROR_SUCCESS ? (SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE) : er;
return WcaFinalize(er);
}
UINT __stdcall TerminateProcessesCA(MSIHANDLE hInstall)
{
HRESULT hr = S_OK;

View File

@@ -28,3 +28,7 @@ EXPORTS
UninstallCommandNotFoundModuleCA
UpgradeCommandNotFoundModuleCA
UnsetAdvancedPasteAPIKeyCA
CleanImageResizerRuntimeRegistryCA
CleanFileLocksmithRuntimeRegistryCA
CleanPowerRenameRuntimeRegistryCA
CleanNewPlusRuntimeRegistryCA

View File

@@ -2,6 +2,8 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Threading.Tasks;
namespace Microsoft.PowerToys.UITest
{
/// <summary>
@@ -25,8 +27,9 @@ namespace Microsoft.PowerToys.UITest
/// </summary>
/// <param name="value">The text to set.</param>
/// <param name="clearText">A value indicating whether to clear the text before setting it. Default value is true</param>
/// <param name="charDelayMS">Delay in milliseconds between each character. Default is 0 (no delay).</param>
/// <returns>The current TextBox instance.</returns>
public TextBox SetText(string value, bool clearText = true)
public TextBox SetText(string value, bool clearText = true, int charDelayMS = 0)
{
if (clearText)
{
@@ -39,10 +42,36 @@ namespace Microsoft.PowerToys.UITest
Task.Delay(500).Wait();
}
PerformAction((actions, windowElement) =>
// TODO: CmdPal bug when inputting text, characters are swallowed too quickly.
// This should be fixed within CmdPal itself.
// Temporary workaround: introduce a delay between character inputs to avoid the issue
if (charDelayMS > 0 || EnvironmentConfig.IsInPipeline)
{
windowElement.SendKeys(value);
});
// Send text character by character with delay (if specified or in pipeline)
PerformAction((actions, windowElement) =>
{
foreach (char c in value)
{
windowElement.SendKeys(c.ToString());
if (charDelayMS > 0)
{
Task.Delay(charDelayMS).Wait();
}
else if (EnvironmentConfig.IsInPipeline)
{
Task.Delay(50).Wait();
}
}
});
}
else
{
// No character delay - send all text at once (original behavior)
PerformAction((actions, windowElement) =>
{
windowElement.SendKeys(value);
});
}
return this;
}

View File

@@ -0,0 +1,45 @@
// 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;
namespace Microsoft.PowerToys.UITest
{
/// <summary>
/// Centralized configuration for all environment variables used in UI tests.
/// </summary>
public static class EnvironmentConfig
{
private static readonly Lazy<bool> _isInPipeline = new(() =>
!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("platform")));
private static readonly Lazy<bool> _useInstallerForTest = new(() =>
{
string? envValue = Environment.GetEnvironmentVariable("useInstallerForTest") ??
Environment.GetEnvironmentVariable("USEINSTALLERFORTEST");
return !string.IsNullOrEmpty(envValue) && bool.TryParse(envValue, out bool result) && result;
});
private static readonly Lazy<string?> _platform = new(() =>
Environment.GetEnvironmentVariable("platform"));
/// <summary>
/// Gets a value indicating whether the tests are running in a CI/CD pipeline.
/// Determined by the presence of the "platform" environment variable.
/// </summary>
public static bool IsInPipeline => _isInPipeline.Value;
/// <summary>
/// Gets a value indicating whether to use installer paths for testing.
/// Checks both "useInstallerForTest" and "USEINSTALLERFORTEST" environment variables.
/// </summary>
public static bool UseInstallerForTest => _useInstallerForTest.Value;
/// <summary>
/// Gets the platform name from the environment variable.
/// Typically used in CI/CD pipelines to identify the build platform.
/// </summary>
public static string? Platform => _platform.Value;
}
}

View File

@@ -92,9 +92,7 @@ namespace Microsoft.PowerToys.UITest
private ModuleConfigData()
{
// 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;
UseInstallerForTest = EnvironmentConfig.UseInstallerForTest;
// Module information including executable name, window name, and optional subdirectory
ModuleInfo = new Dictionary<PowerToysModule, ModuleInfo>

View File

@@ -5,6 +5,7 @@
using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
@@ -37,6 +38,9 @@ namespace Microsoft.PowerToys.UITest
private PowerToysModule scope;
private string[]? commandLineArgs;
/// <summary>
/// Gets a value indicating whether to use installer paths for testing.
/// </summary>
private bool UseInstallerForTest { get; }
[UnconditionalSuppressMessage("SingleFile", "IL3000:Avoid accessing Assembly file path when publishing as a single file", Justification = "<Pending>")]
@@ -45,9 +49,7 @@ namespace Microsoft.PowerToys.UITest
this.scope = scope;
this.commandLineArgs = commandLineArgs;
this.sessionPath = ModuleConfigData.Instance.GetModulePath(scope);
string? useInstallerForTestEnv =
Environment.GetEnvironmentVariable("useInstallerForTest") ?? Environment.GetEnvironmentVariable("USEINSTALLERFORTEST");
UseInstallerForTest = !string.IsNullOrEmpty(useInstallerForTestEnv) && bool.TryParse(useInstallerForTestEnv, out bool result) && result;
UseInstallerForTest = EnvironmentConfig.UseInstallerForTest;
this.locationPath = UseInstallerForTest ? string.Empty : Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
CheckWinAppDriverAndRoot();
@@ -136,6 +138,10 @@ namespace Microsoft.PowerToys.UITest
{
TryLaunchPowerToysSettings(opts);
}
else if (scope == PowerToysModule.CommandPalette && UseInstallerForTest)
{
TryLaunchCommandPalette(opts);
}
else
{
opts.AddAdditionalCapability("app", appPath);
@@ -163,48 +169,77 @@ namespace Microsoft.PowerToys.UITest
private void TryLaunchPowerToysSettings(AppiumOptions opts)
{
CheckWinAppDriverAndRoot();
var runnerProcessInfo = new ProcessStartInfo
try
{
FileName = locationPath + runnerPath,
Verb = "runas",
Arguments = "--open-settings",
};
ExitExe(runnerProcessInfo.FileName);
runner = Process.Start(runnerProcessInfo);
Thread.Sleep(5000);
// Exit CmdPal UI before launching new process if use installer for test
ExitExeByName("Microsoft.CmdPal.UI");
if (root != null)
{
const int maxRetries = 5;
const int delayMs = 5000;
var windowName = "PowerToys Settings";
for (int attempt = 1; attempt <= maxRetries; attempt++)
var runnerProcessInfo = new ProcessStartInfo
{
var settingsWindow = ApiHelper.FindDesktopWindowHandler(
[windowName, AdministratorPrefix + windowName]);
FileName = locationPath + runnerPath,
Verb = "runas",
Arguments = "--open-settings",
};
if (settingsWindow.Count > 0)
{
var hexHwnd = settingsWindow[0].HWnd.ToString("x");
opts.AddAdditionalCapability("appTopLevelWindow", hexHwnd);
return;
}
ExitExe(runnerProcessInfo.FileName);
runner = Process.Start(runnerProcessInfo);
if (attempt < maxRetries)
{
Thread.Sleep(delayMs);
}
else
{
throw new TimeoutException("Failed to find PowerToys Settings window after multiple attempts.");
}
WaitForWindowAndSetCapability(opts, "PowerToys Settings", 5000, 5);
// Exit CmdPal UI before launching new process if use installer for test
ExitExeByName("Microsoft.CmdPal.UI");
}
catch (Exception ex)
{
throw new InvalidOperationException($"Failed to launch PowerToys Settings: {ex.Message}", ex);
}
}
private void TryLaunchCommandPalette(AppiumOptions opts)
{
try
{
// Exit any existing CmdPal UI process
ExitExeByName("Microsoft.CmdPal.UI");
var processStartInfo = new ProcessStartInfo
{
FileName = "cmd.exe",
Arguments = "/c start shell:appsFolder\\Microsoft.CommandPalette_8wekyb3d8bbwe!App",
UseShellExecute = false,
CreateNoWindow = true,
WindowStyle = ProcessWindowStyle.Hidden,
};
var process = Process.Start(processStartInfo);
process?.WaitForExit();
WaitForWindowAndSetCapability(opts, "Command Palette", 5000, 10);
}
catch (Exception ex)
{
throw new InvalidOperationException($"Failed to launch Command Palette: {ex.Message}", ex);
}
}
private void WaitForWindowAndSetCapability(AppiumOptions opts, string windowName, int delayMs, int maxRetries)
{
for (int attempt = 1; attempt <= maxRetries; attempt++)
{
var window = ApiHelper.FindDesktopWindowHandler(
[windowName, AdministratorPrefix + windowName]);
if (window.Count > 0)
{
var hexHwnd = window[0].HWnd.ToString("x");
opts.AddAdditionalCapability("appTopLevelWindow", hexHwnd);
return;
}
if (attempt < maxRetries)
{
Thread.Sleep(delayMs);
}
else
{
throw new TimeoutException($"Failed to find {windowName} window after multiple attempts.");
}
}
}

View File

@@ -5,6 +5,7 @@
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using OpenQA.Selenium;
@@ -20,6 +21,9 @@ namespace Microsoft.PowerToys.UITest
public required Session Session { get; set; }
/// <summary>
/// Gets a value indicating whether the tests are running in a CI/CD pipeline.
/// </summary>
public bool IsInPipeline { get; }
public string? ScreenshotDirectory { get; set; }
@@ -34,8 +38,8 @@ namespace Microsoft.PowerToys.UITest
public UITestBase(PowerToysModule scope = PowerToysModule.PowerToysSettings, WindowSize size = WindowSize.UnSpecified, string[]? commandLineArgs = null)
{
this.IsInPipeline = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("platform"));
Console.WriteLine($"Running tests on platform: {Environment.GetEnvironmentVariable("platform")}");
this.IsInPipeline = EnvironmentConfig.IsInPipeline;
Console.WriteLine($"Running tests on platform: {EnvironmentConfig.Platform}");
if (IsInPipeline)
{
NativeMethods.ChangeDisplayResolution(1920, 1080);
@@ -56,6 +60,7 @@ namespace Microsoft.PowerToys.UITest
[TestInitialize]
public void TestInit()
{
KeyboardHelper.SendKeys(Key.Win, Key.M);
CloseOtherApplications();
if (IsInPipeline)
{
@@ -247,6 +252,174 @@ namespace Microsoft.PowerToys.UITest
return this.Session.Has<Element>(name, timeoutMS, global);
}
/// <summary>
/// Finds an element using partial name matching (contains).
/// Useful for finding windows with variable titles like "filename.txt - Notepad" or "filename - Notepad".
/// </summary>
/// <typeparam name="T">The class of the element, should be Element or its derived class.</typeparam>
/// <param name="partialName">Part of the name to search for.</param>
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
/// <returns>The found element.</returns>
protected T FindByPartialName<T>(string partialName, int timeoutMS = 5000, bool global = false)
where T : Element, new()
{
return Session.Find<T>(By.XPath($"//*[contains(@Name, '{partialName}')]"), timeoutMS, global);
}
/// <summary>
/// Finds an element using partial name matching (contains).
/// </summary>
/// <param name="partialName">Part of the name to search for.</param>
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
/// <returns>The found element.</returns>
protected Element FindByPartialName(string partialName, int timeoutMS = 5000, bool global = false)
{
return FindByPartialName<Element>(partialName, timeoutMS, global);
}
/// <summary>
/// Base method for finding elements by selector and filtering by name pattern.
/// </summary>
/// <typeparam name="T">The class of the element, should be Element or its derived class.</typeparam>
/// <param name="selector">The selector to find initial candidates.</param>
/// <param name="namePattern">Pattern to match against the Name attribute. Supports regex patterns.</param>
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
/// <param name="errorMessage">Custom error message when no element is found.</param>
/// <returns>The found element.</returns>
private T FindByNamePattern<T>(By selector, string namePattern, int timeoutMS = 5000, bool global = false, string? errorMessage = null)
where T : Element, new()
{
var elements = Session.FindAll<T>(selector, timeoutMS, global);
var regex = new Regex(namePattern, RegexOptions.IgnoreCase);
foreach (var element in elements)
{
var name = element.GetAttribute("Name");
if (!string.IsNullOrEmpty(name) && regex.IsMatch(name))
{
return element;
}
}
throw new NoSuchElementException(errorMessage ?? $"No element found matching pattern: {namePattern}");
}
/// <summary>
/// Finds an element using regular expression pattern matching.
/// </summary>
/// <typeparam name="T">The class of the element, should be Element or its derived class.</typeparam>
/// <param name="pattern">Regular expression pattern to match against the Name attribute.</param>
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
/// <returns>The found element.</returns>
protected T FindByPattern<T>(string pattern, int timeoutMS = 5000, bool global = false)
where T : Element, new()
{
return FindByNamePattern<T>(By.XPath("//*[@Name]"), pattern, timeoutMS, global, $"No element found matching pattern: {pattern}");
}
/// <summary>
/// Finds an element using regular expression pattern matching.
/// </summary>
/// <param name="pattern">Regular expression pattern to match against the Name attribute.</param>
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
/// <returns>The found element.</returns>
protected Element FindByPattern(string pattern, int timeoutMS = 5000, bool global = false)
{
return FindByPattern<Element>(pattern, timeoutMS, global);
}
/// <summary>
/// Finds an element by ClassName only.
/// Returns the first element found with the specified ClassName.
/// </summary>
/// <typeparam name="T">The class of the element, should be Element or its derived class.</typeparam>
/// <param name="className">The ClassName to search for (e.g., "Notepad", "CabinetWClass").</param>
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
/// <returns>The found element.</returns>
protected T FindByClassName<T>(string className, int timeoutMS = 5000, bool global = false)
where T : Element, new()
{
return Session.Find<T>(By.ClassName(className), timeoutMS, global);
}
/// <summary>
/// Finds an element by ClassName only.
/// Returns the first element found with the specified ClassName.
/// </summary>
/// <param name="className">The ClassName to search for (e.g., "Notepad", "CabinetWClass").</param>
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
/// <returns>The found element.</returns>
protected Element FindByClassName(string className, int timeoutMS = 5000, bool global = false)
{
return FindByClassName<Element>(className, timeoutMS, global);
}
/// <summary>
/// Finds an element by ClassName and matches its Name attribute using regex pattern matching.
/// </summary>
/// <typeparam name="T">The class of the element, should be Element or its derived class.</typeparam>
/// <param name="className">The ClassName to search for (e.g., "Notepad", "CabinetWClass").</param>
/// <param name="namePattern">Pattern to match against the Name attribute. Supports regex patterns.</param>
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
/// <returns>The found element.</returns>
protected T FindByClassNameAndNamePattern<T>(string className, string namePattern, int timeoutMS = 5000, bool global = false)
where T : Element, new()
{
return FindByNamePattern<T>(By.ClassName(className), namePattern, timeoutMS, global, $"No element with ClassName '{className}' found matching name pattern: {namePattern}");
}
/// <summary>
/// Finds an element by ClassName and matches its Name attribute using regex pattern matching.
/// </summary>
/// <param name="className">The ClassName to search for (e.g., "Notepad", "CabinetWClass").</param>
/// <param name="namePattern">Pattern to match against the Name attribute. Supports regex patterns.</param>
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
/// <returns>The found element.</returns>
protected Element FindByClassNameAndNamePattern(string className, string namePattern, int timeoutMS = 5000, bool global = false)
{
return FindByClassNameAndNamePattern<Element>(className, namePattern, timeoutMS, global);
}
/// <summary>
/// Finds a Notepad window regardless of whether the file extension is shown in the title.
/// Handles both "filename.txt - Notepad" and "filename - Notepad" formats.
/// Uses ClassName to efficiently find Notepad windows first, then matches the filename.
/// </summary>
/// <param name="baseFileName">The base filename without extension (e.g., "test" for "test.txt").</param>
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
/// <returns>The found Notepad window element.</returns>
protected Element FindNotepadWindow(string baseFileName, int timeoutMS = 5000, bool global = false)
{
string pattern = $@"^{Regex.Escape(baseFileName)}(\.\w+)?(\s*-\s*|\s+)Notepad$";
return FindByClassNameAndNamePattern("Notepad", pattern, timeoutMS, global);
}
/// <summary>
/// Finds an Explorer window regardless of the folder or file name display format.
/// Handles various Explorer window title formats like "FolderName", "FileName", "FolderName - File Explorer", etc.
/// Uses ClassName to efficiently find Explorer windows first, then matches the folder or file name.
/// </summary>
/// <param name="folderName">The folder or file name to search for (e.g., "Documents", "Desktop", "test.txt").</param>
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
/// <returns>The found Explorer window element.</returns>
protected Element FindExplorerWindow(string folderName, int timeoutMS = 5000, bool global = false)
{
string pattern = $@"^{Regex.Escape(folderName)}(\s*-\s*(File\s+Explorer|Windows\s+Explorer))?$";
return FindByClassNameAndNamePattern("CabinetWClass", pattern, timeoutMS, global);
}
/// <summary>
/// Finds an Explorer window by partial folder path.
/// Useful when the full path might be displayed in the title.
/// </summary>
/// <param name="partialPath">Part of the folder path to search for.</param>
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
/// <returns>The found Explorer window element.</returns>
protected Element FindExplorerByPartialPath(string partialPath, int timeoutMS = 5000, bool global = false)
{
return FindByPartialName(partialPath, timeoutMS, global);
}
/// <summary>
/// Finds all elements by selector.
/// Shortcut for this.Session.FindAll<T>(by, timeoutMS)

View File

@@ -27,10 +27,8 @@ namespace Microsoft.PowerToys.UITest
[RequiresUnreferencedCode("This method uses reflection which may not be compatible with trimming.")]
public static void AreEqual(TestContext? testContext, Element element, string scenarioSubname = "")
{
var pipelinePlatform = Environment.GetEnvironmentVariable("platform");
// Perform visual validation only in the pipeline
if (string.IsNullOrEmpty(pipelinePlatform))
if (!EnvironmentConfig.IsInPipeline)
{
Console.WriteLine("Skip visual validation in the local run.");
return;
@@ -55,11 +53,11 @@ namespace Microsoft.PowerToys.UITest
if (string.IsNullOrWhiteSpace(scenarioSubname))
{
scenarioSubname = string.Join("_", callerClassName, callerName, pipelinePlatform);
scenarioSubname = string.Join("_", callerClassName, callerName, EnvironmentConfig.Platform);
}
else
{
scenarioSubname = string.Join("_", callerClassName, callerName, scenarioSubname.Trim(), pipelinePlatform);
scenarioSubname = string.Join("_", callerClassName, callerName, scenarioSubname.Trim(), EnvironmentConfig.Platform);
}
var baselineImageResourceName = callerMethod!.DeclaringType!.Assembly.GetManifestResourceNames().Where(name => name.Contains(scenarioSubname)).FirstOrDefault();

View File

@@ -0,0 +1,266 @@
// Shared runtime shell extension registration utility for PowerToys modules.
// Provides a generic EnsureRegistered function so individual modules only need
// to supply a specification (CLSID, sentinel, handler key paths, etc.).
#pragma once
#include <string>
#include <vector>
#include <windows.h>
#include <shlwapi.h>
#include "../logger/logger.h"
namespace runtime_shell_ext
{
struct Spec
{
// Mandatory
std::wstring clsid; // e.g. {GUID}
std::wstring sentinelKey; // e.g. Software\\Microsoft\\PowerToys\\ModuleName
std::wstring sentinelValue; // e.g. ContextMenuRegistered
std::vector<std::wstring> dllFileCandidates; // relative filenames (pick first existing)
std::vector<std::wstring> contextMenuHandlerKeyPaths; // full HKCU relative paths where default value = CLSID
// Optional
std::wstring friendlyName; // if non-empty written as default under CLSID root
bool writeOptInEmptyValue = true; // write ContextMenuOptIn="" under CLSID root (legacy pattern)
bool writeThreadingModel = true; // write Apartment threading model
std::vector<std::wstring> extraAssociationPaths; // additional key paths (DragDropHandlers etc.) default=CLSID
std::vector<std::wstring> systemFileAssocExtensions; // e.g. .png -> Software\\Classes\\SystemFileAssociations\\.png\\ShellEx\\ContextMenuHandlers\\<HandlerName>
std::wstring systemFileAssocHandlerName; // e.g. ImageResizer
std::wstring representativeSystemExt; // used to decide if associations need repair (.png)
bool logRepairs = true;
};
namespace detail
{
// Minimal RAII wrapper for HKEY
struct unique_hkey
{
HKEY h{ nullptr };
unique_hkey() = default;
explicit unique_hkey(HKEY handle) : h(handle) {}
~unique_hkey() { if (h) RegCloseKey(h); }
unique_hkey(const unique_hkey&) = delete;
unique_hkey& operator=(const unique_hkey&) = delete;
unique_hkey(unique_hkey&& other) noexcept : h(other.h) { other.h = nullptr; }
unique_hkey& operator=(unique_hkey&& other) noexcept { if (this != &other) { if (h) RegCloseKey(h); h = other.h; other.h = nullptr; } return *this; }
HKEY get() const { return h; }
HKEY* put() { if (h) { RegCloseKey(h); h = nullptr; } return &h; }
};
inline std::wstring base_dir_from_module(HMODULE h)
{
wchar_t buf[MAX_PATH];
if (GetModuleFileNameW(h, buf, MAX_PATH))
{
PathRemoveFileSpecW(buf);
return buf;
}
return L"";
}
inline std::wstring pick_existing_dll(const std::wstring& base, const std::vector<std::wstring>& candidates)
{
for (const auto& rel : candidates)
{
std::wstring full = base + L"\\" + rel;
if (GetFileAttributesW(full.c_str()) != INVALID_FILE_ATTRIBUTES)
{
return full;
}
}
if (!candidates.empty())
{
return base + L"\\" + candidates.front();
}
return L"";
}
inline bool sentinel_exists(const Spec& spec)
{
unique_hkey key;
if (RegOpenKeyExW(HKEY_CURRENT_USER, spec.sentinelKey.c_str(), 0, KEY_READ, key.put()) != ERROR_SUCCESS)
return false;
DWORD v = 0; DWORD sz = sizeof(v);
return RegQueryValueExW(key.get(), spec.sentinelValue.c_str(), nullptr, nullptr, reinterpret_cast<LPBYTE>(&v), &sz) == ERROR_SUCCESS && v == 1;
}
inline void write_sentinel(const Spec& spec)
{
unique_hkey key;
if (RegCreateKeyExW(HKEY_CURRENT_USER, spec.sentinelKey.c_str(), 0, nullptr, 0, KEY_WRITE, nullptr, key.put(), nullptr) == ERROR_SUCCESS)
{
DWORD one = 1;
RegSetValueExW(key.get(), spec.sentinelValue.c_str(), 0, REG_DWORD, reinterpret_cast<const BYTE*>(&one), sizeof(one));
}
}
inline void write_inproc_server(const Spec& spec, const std::wstring& dllPath)
{
using namespace std::string_literals;
std::wstring clsidRoot = L"Software\\Classes\\CLSID\\"s + spec.clsid;
std::wstring inprocKey = clsidRoot + L"\\InprocServer32";
{
unique_hkey key;
if (RegCreateKeyExW(HKEY_CURRENT_USER, clsidRoot.c_str(), 0, nullptr, 0, KEY_WRITE, nullptr, key.put(), nullptr) == ERROR_SUCCESS)
{
if (!spec.friendlyName.empty())
{
RegSetValueExW(key.get(), nullptr, 0, REG_SZ, reinterpret_cast<const BYTE*>(spec.friendlyName.c_str()), static_cast<DWORD>((spec.friendlyName.size() + 1) * sizeof(wchar_t)));
}
if (spec.writeOptInEmptyValue)
{
const wchar_t* optIn = L"ContextMenuOptIn";
const wchar_t empty = L'\0';
RegSetValueExW(key.get(), optIn, 0, REG_SZ, reinterpret_cast<const BYTE*>(&empty), sizeof(empty));
}
}
}
unique_hkey key;
if (RegCreateKeyExW(HKEY_CURRENT_USER, inprocKey.c_str(), 0, nullptr, 0, KEY_WRITE, nullptr, key.put(), nullptr) == ERROR_SUCCESS)
{
RegSetValueExW(key.get(), nullptr, 0, REG_SZ, reinterpret_cast<const BYTE*>(dllPath.c_str()), static_cast<DWORD>((dllPath.size() + 1) * sizeof(wchar_t)));
if (spec.writeThreadingModel)
{
const wchar_t* tm = L"Apartment";
RegSetValueExW(key.get(), L"ThreadingModel", 0, REG_SZ, reinterpret_cast<const BYTE*>(tm), static_cast<DWORD>((wcslen(tm) + 1) * sizeof(wchar_t)));
}
}
}
inline std::wstring read_inproc_server(const Spec& spec)
{
using namespace std::string_literals;
std::wstring inprocKey = L"Software\\Classes\\CLSID\\"s + spec.clsid + L"\\InprocServer32";
unique_hkey key;
if (RegOpenKeyExW(HKEY_CURRENT_USER, inprocKey.c_str(), 0, KEY_READ, key.put()) != ERROR_SUCCESS)
return L"";
wchar_t buf[MAX_PATH]; DWORD sz = sizeof(buf);
if (RegQueryValueExW(key.get(), nullptr, nullptr, nullptr, reinterpret_cast<LPBYTE>(buf), &sz) == ERROR_SUCCESS)
return std::wstring(buf);
return L"";
}
inline void write_default_value_key(const std::wstring& keyPath, const std::wstring& value)
{
unique_hkey key;
if (RegCreateKeyExW(HKEY_CURRENT_USER, keyPath.c_str(), 0, nullptr, 0, KEY_WRITE, nullptr, key.put(), nullptr) == ERROR_SUCCESS)
{
RegSetValueExW(key.get(), nullptr, 0, REG_SZ, reinterpret_cast<const BYTE*>(value.c_str()), static_cast<DWORD>((value.size() + 1) * sizeof(wchar_t)));
}
}
inline bool representative_association_exists(const Spec& spec)
{
using namespace std::string_literals;
if (spec.representativeSystemExt.empty() || spec.systemFileAssocHandlerName.empty())
return true;
std::wstring keyPath = L"Software\\Classes\\SystemFileAssociations\\"s + spec.representativeSystemExt + L"\\ShellEx\\ContextMenuHandlers\\" + spec.systemFileAssocHandlerName;
unique_hkey key;
return RegOpenKeyExW(HKEY_CURRENT_USER, keyPath.c_str(), 0, KEY_READ, key.put()) == ERROR_SUCCESS;
}
}
inline bool EnsureRegistered(const Spec& spec, HMODULE moduleInstance)
{
using namespace std::string_literals;
auto base = detail::base_dir_from_module(moduleInstance);
auto dllPath = detail::pick_existing_dll(base, spec.dllFileCandidates);
if (dllPath.empty())
{
Logger::error(L"Runtime registration: cannot locate dll path for CLSID {}", spec.clsid);
return false;
}
bool exists = detail::sentinel_exists(spec);
bool repaired = false;
if (exists)
{
auto current = detail::read_inproc_server(spec);
if (_wcsicmp(current.c_str(), dllPath.c_str()) != 0)
{
detail::write_inproc_server(spec, dllPath);
repaired = true;
}
if (!detail::representative_association_exists(spec))
{
repaired = true;
}
}
if (!exists)
{
detail::write_inproc_server(spec, dllPath);
}
if (!exists || repaired)
{
for (const auto& path : spec.contextMenuHandlerKeyPaths)
{
detail::write_default_value_key(path, spec.clsid);
}
for (const auto& path : spec.extraAssociationPaths)
{
detail::write_default_value_key(path, spec.clsid);
}
if (!spec.systemFileAssocExtensions.empty() && !spec.systemFileAssocHandlerName.empty())
{
for (const auto& ext : spec.systemFileAssocExtensions)
{
std::wstring path = L"Software\\Classes\\SystemFileAssociations\\"s + ext + L"\\ShellEx\\ContextMenuHandlers\\" + spec.systemFileAssocHandlerName;
detail::write_default_value_key(path, spec.clsid);
}
}
}
if (!exists)
{
detail::write_sentinel(spec);
Logger::info(L"Runtime registration completed for CLSID {}", spec.clsid);
}
else if (repaired && spec.logRepairs)
{
Logger::info(L"Runtime registration repaired for CLSID {}", spec.clsid);
}
return true;
}
inline bool Unregister(const Spec& spec)
{
using namespace std::string_literals;
// Remove handler key paths
for (const auto& path : spec.contextMenuHandlerKeyPaths)
{
RegDeleteTreeW(HKEY_CURRENT_USER, path.c_str());
}
// Remove extra association paths (e.g., drag & drop handlers)
for (const auto& path : spec.extraAssociationPaths)
{
RegDeleteTreeW(HKEY_CURRENT_USER, path.c_str());
}
// Remove per-extension system file association handler keys
if (!spec.systemFileAssocExtensions.empty() && !spec.systemFileAssocHandlerName.empty())
{
for (const auto& ext : spec.systemFileAssocExtensions)
{
std::wstring keyPath = L"Software\\Classes\\SystemFileAssociations\\"s + ext + L"\\ShellEx\\ContextMenuHandlers\\" + spec.systemFileAssocHandlerName;
RegDeleteTreeW(HKEY_CURRENT_USER, keyPath.c_str());
}
}
// Remove CLSID branch
if (!spec.clsid.empty())
{
std::wstring clsidRoot = L"Software\\Classes\\CLSID\\"s + spec.clsid;
RegDeleteTreeW(HKEY_CURRENT_USER, clsidRoot.c_str());
}
// Remove sentinel value (not deleting entire key to avoid disturbing other values)
if (!spec.sentinelKey.empty() && !spec.sentinelValue.empty())
{
HKEY hKey{};
if (RegOpenKeyExW(HKEY_CURRENT_USER, spec.sentinelKey.c_str(), 0, KEY_SET_VALUE, &hKey) == ERROR_SUCCESS)
{
RegDeleteValueW(hKey, spec.sentinelValue.c_str());
RegCloseKey(hKey);
}
}
Logger::info(L"Successfully unregistered CLSID {}", spec.clsid);
return true;
}
}

View File

@@ -73,6 +73,7 @@
<ClInclude Include="ClassFactory.h" />
<ClInclude Include="dllmain.h" />
<ClInclude Include="ExplorerCommand.h" />
<ClInclude Include="RuntimeRegistration.h" />
<ClInclude Include="pch.h" />
<None Include="packages.config" />
<None Include="resource.base.h" />

View File

@@ -27,6 +27,9 @@
<ClInclude Include="dllmain.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="RuntimeRegistration.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="dllmain.cpp">

View File

@@ -12,6 +12,7 @@
#include "FileLocksmithLib/Constants.h"
#include "FileLocksmithLib/Settings.h"
#include "FileLocksmithLib/Trace.h"
#include "RuntimeRegistration.h"
#include "dllmain.h"
#include "Generated Files/resource.h"
@@ -82,12 +83,17 @@ public:
{
std::wstring path = get_module_folderpath(globals::instance);
std::wstring packageUri = path + L"\\FileLocksmithContextMenuPackage.msix";
if (!package::IsPackageRegisteredWithPowerToysVersion(constants::nonlocalizable::ContextMenuPackageName))
{
package::RegisterSparsePackage(path, packageUri);
}
}
else
{
#if defined(ENABLE_REGISTRATION) || defined(NDEBUG)
FileLocksmithRuntimeRegistration::EnsureRegistered();
#endif
}
m_enabled = true;
}
@@ -95,6 +101,13 @@ public:
virtual void disable() override
{
Logger::info(L"File Locksmith disabled");
if (!package::IsWin11OrGreater())
{
#if defined(ENABLE_REGISTRATION) || defined(NDEBUG)
FileLocksmithRuntimeRegistration::Unregister();
Logger::info(L"File Locksmith context menu unregistered (Win10)");
#endif
}
m_enabled = false;
}

View File

@@ -0,0 +1,36 @@
// Header-only runtime registration for FileLocksmith context menu extension.
#pragma once
#include <common/utils/shell_ext_registration.h>
namespace globals { extern HMODULE instance; }
namespace FileLocksmithRuntimeRegistration
{
namespace
{
inline runtime_shell_ext::Spec BuildSpec()
{
runtime_shell_ext::Spec spec;
spec.clsid = L"{84D68575-E186-46AD-B0CB-BAEB45EE29C0}";
spec.sentinelKey = L"Software\\Microsoft\\PowerToys\\FileLocksmith";
spec.sentinelValue = L"ContextMenuRegistered";
spec.dllFileCandidates = { L"PowerToys.FileLocksmithExt.dll" };
spec.contextMenuHandlerKeyPaths = {
L"Software\\Classes\\AllFileSystemObjects\\ShellEx\\ContextMenuHandlers\\FileLocksmithExt",
L"Software\\Classes\\Drive\\ShellEx\\ContextMenuHandlers\\FileLocksmithExt" };
spec.friendlyName = L"File Locksmith Shell Extension";
return spec;
}
}
inline bool EnsureRegistered()
{
return runtime_shell_ext::EnsureRegistered(BuildSpec(), globals::instance);
}
inline void Unregister()
{
runtime_shell_ext::Unregister(BuildSpec());
}
}

View File

@@ -5,6 +5,7 @@
#include "MouseHighlighter.h"
#include "trace.h"
#include <cmath>
#include <algorithm>
#ifdef COMPOSITION
namespace winrt
@@ -49,6 +50,9 @@ private:
void BringToFront();
HHOOK m_mouseHook = NULL;
static LRESULT CALLBACK MouseHookProc(int nCode, WPARAM wParam, LPARAM lParam) noexcept;
// Helpers for spotlight overlay
float GetDpiScale() const;
void UpdateSpotlightMask(float cx, float cy, float radius, bool show);
static constexpr auto m_className = L"MouseHighlighter";
static constexpr auto m_windowTitle = L"PowerToys Mouse Highlighter";
@@ -67,7 +71,14 @@ private:
winrt::CompositionSpriteShape m_leftPointer{ nullptr };
winrt::CompositionSpriteShape m_rightPointer{ nullptr };
winrt::CompositionSpriteShape m_alwaysPointer{ nullptr };
winrt::CompositionSpriteShape m_spotlightPointer{ nullptr };
// Spotlight overlay (mask with soft feathered edge)
winrt::SpriteVisual m_overlay{ nullptr };
winrt::CompositionMaskBrush m_spotlightMask{ nullptr };
winrt::CompositionRadialGradientBrush m_spotlightMaskGradient{ nullptr };
winrt::CompositionColorBrush m_spotlightSource{ nullptr };
winrt::CompositionColorGradientStop m_maskStopCenter{ nullptr };
winrt::CompositionColorGradientStop m_maskStopInner{ nullptr };
winrt::CompositionColorGradientStop m_maskStopOuter{ nullptr };
bool m_leftPointerEnabled = true;
bool m_rightPointerEnabled = true;
@@ -123,6 +134,35 @@ bool Highlighter::CreateHighlighter()
m_shape.RelativeSizeAdjustment({ 1.0f, 1.0f });
m_root.Children().InsertAtTop(m_shape);
// Create spotlight overlay (soft feather, DPI-aware)
m_overlay = m_compositor.CreateSpriteVisual();
m_overlay.RelativeSizeAdjustment({ 1.0f, 1.0f });
m_spotlightSource = m_compositor.CreateColorBrush(m_alwaysColor);
m_spotlightMaskGradient = m_compositor.CreateRadialGradientBrush();
m_spotlightMaskGradient.MappingMode(winrt::CompositionMappingMode::Absolute);
// Center region fully transparent
m_maskStopCenter = m_compositor.CreateColorGradientStop();
m_maskStopCenter.Offset(0.0f);
m_maskStopCenter.Color(winrt::Windows::UI::ColorHelper::FromArgb(0, 0, 0, 0));
// Inner edge of feather (still transparent)
m_maskStopInner = m_compositor.CreateColorGradientStop();
m_maskStopInner.Offset(0.995f); // will be updated per-radius
m_maskStopInner.Color(winrt::Windows::UI::ColorHelper::FromArgb(0, 0, 0, 0));
// Outer edge (opaque mask -> overlay visible)
m_maskStopOuter = m_compositor.CreateColorGradientStop();
m_maskStopOuter.Offset(1.0f);
m_maskStopOuter.Color(winrt::Windows::UI::ColorHelper::FromArgb(255, 255, 255, 255));
m_spotlightMaskGradient.ColorStops().Append(m_maskStopCenter);
m_spotlightMaskGradient.ColorStops().Append(m_maskStopInner);
m_spotlightMaskGradient.ColorStops().Append(m_maskStopOuter);
m_spotlightMask = m_compositor.CreateMaskBrush();
m_spotlightMask.Source(m_spotlightSource);
m_spotlightMask.Mask(m_spotlightMaskGradient);
m_overlay.Brush(m_spotlightMask);
m_overlay.IsVisible(false);
m_root.Children().InsertAtTop(m_overlay);
return true;
}
catch (...)
@@ -165,12 +205,8 @@ void Highlighter::AddDrawingPoint(MouseButton button)
// always
if (m_spotlightMode)
{
float borderThickness = static_cast<float>(std::hypot(GetSystemMetrics(SM_CXVIRTUALSCREEN), GetSystemMetrics(SM_CYVIRTUALSCREEN)));
circleGeometry.Radius({ static_cast<float>(borderThickness / 2.0 + m_radius), static_cast<float>(borderThickness / 2.0 + m_radius) });
circleShape.FillBrush(nullptr);
circleShape.StrokeBrush(m_compositor.CreateColorBrush(m_alwaysColor));
circleShape.StrokeThickness(borderThickness);
m_spotlightPointer = circleShape;
UpdateSpotlightMask(static_cast<float>(pt.x), static_cast<float>(pt.y), m_radius, true);
return;
}
else
{
@@ -209,20 +245,14 @@ void Highlighter::UpdateDrawingPointPosition(MouseButton button)
}
else
{
// always
// always / spotlight idle
if (m_spotlightMode)
{
if (m_spotlightPointer)
{
m_spotlightPointer.Offset({ static_cast<float>(pt.x), static_cast<float>(pt.y) });
}
UpdateSpotlightMask(static_cast<float>(pt.x), static_cast<float>(pt.y), m_radius, true);
}
else
else if (m_alwaysPointer)
{
if (m_alwaysPointer)
{
m_alwaysPointer.Offset({ static_cast<float>(pt.x), static_cast<float>(pt.y) });
}
m_alwaysPointer.Offset({ static_cast<float>(pt.x), static_cast<float>(pt.y) });
}
}
}
@@ -266,9 +296,9 @@ void Highlighter::ClearDrawingPoint()
{
if (m_spotlightMode)
{
if (m_spotlightPointer)
if (m_overlay)
{
m_spotlightPointer.StrokeBrush().as<winrt::Windows::UI::Composition::CompositionColorBrush>().Color(winrt::Windows::UI::ColorHelper::FromArgb(0, 0, 0, 0));
m_overlay.IsVisible(false);
}
}
else
@@ -421,7 +451,10 @@ void Highlighter::StopDrawing()
m_leftPointer = nullptr;
m_rightPointer = nullptr;
m_alwaysPointer = nullptr;
m_spotlightPointer = nullptr;
if (m_overlay)
{
m_overlay.IsVisible(false);
}
ShowWindow(m_hwnd, SW_HIDE);
UnhookWindowsHookEx(m_mouseHook);
ClearDrawing();
@@ -452,6 +485,16 @@ void Highlighter::ApplySettings(MouseHighlighterSettings settings)
m_rightPointerEnabled = false;
}
// Keep spotlight overlay color updated
if (m_spotlightSource)
{
m_spotlightSource.Color(m_alwaysColor);
}
if (!m_spotlightMode && m_overlay)
{
m_overlay.IsVisible(false);
}
if (instance->m_visible)
{
instance->StopDrawing();
@@ -563,6 +606,43 @@ void Highlighter::Terminate()
}
}
float Highlighter::GetDpiScale() const
{
return static_cast<float>(GetDpiForWindow(m_hwnd)) / 96.0f;
}
// Update spotlight radial mask center/radius with DPI-aware feather
void Highlighter::UpdateSpotlightMask(float cx, float cy, float radius, bool show)
{
if (!m_spotlightMaskGradient)
{
return;
}
m_spotlightMaskGradient.EllipseCenter({ cx, cy });
m_spotlightMaskGradient.EllipseRadius({ radius, radius });
const float dpiScale = GetDpiScale();
// Target a very fine edge: ~1 physical pixel, convert to DIPs: 1 / dpiScale
const float featherDip = 1.0f / (dpiScale > 0.0f ? dpiScale : 1.0f);
const float safeRadius = (std::max)(radius, 1.0f);
const float featherRel = (std::min)(0.25f, featherDip / safeRadius);
if (m_maskStopInner)
{
m_maskStopInner.Offset((std::max)(0.0f, 1.0f - featherRel));
}
if (m_spotlightSource)
{
m_spotlightSource.Color(m_alwaysColor);
}
if (m_overlay)
{
m_overlay.IsVisible(show);
}
}
#pragma region MouseHighlighter_API
void MouseHighlighterApplySettings(MouseHighlighterSettings settings)

View File

@@ -123,6 +123,7 @@ MakeAppx.exe pack /d . /p $(OutDir)NewPlusPackage.msix /nv</Command>
<ClInclude Include="settings.h" />
<ClInclude Include="trace.h" />
<ClInclude Include="new_utilities.h" />
<ClInclude Include="RuntimeRegistration.h" />
<ClInclude Include="resource.base.h" />
<ClInclude Include="template_folder.h" />
<ClInclude Include="pch.h" />

View File

@@ -84,6 +84,9 @@
<ClInclude Include="helpers_variables.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="RuntimeRegistration.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />

View File

@@ -0,0 +1,36 @@
// Header-only runtime registration for New+ Win10 context menu.
#pragma once
#include <windows.h>
#include <string>
#include <common/utils/shell_ext_registration.h>
// Provided by dll_main.cpp
extern HMODULE module_instance_handle;
namespace NewPlusRuntimeRegistration
{
namespace {
inline runtime_shell_ext::Spec BuildSpec()
{
runtime_shell_ext::Spec spec;
spec.clsid = L"{FF90D477-E32A-4BE8-8CC5-A502A97F5401}";
spec.sentinelKey = L"Software\\Microsoft\\PowerToys\\NewPlus";
spec.sentinelValue = L"ContextMenuRegisteredWin10";
spec.dllFileCandidates = { L"PowerToys.NewPlus.ShellExtension.win10.dll" };
spec.contextMenuHandlerKeyPaths = { L"Software\\Classes\\Directory\\background\\ShellEx\\ContextMenuHandlers\\NewPlusShellExtensionWin10" };
spec.friendlyName = L"NewPlus Shell Extension Win10";
return spec;
}
}
inline bool EnsureRegisteredWin10()
{
return runtime_shell_ext::EnsureRegistered(BuildSpec(), module_instance_handle);
}
inline void Unregister()
{
runtime_shell_ext::Unregister(BuildSpec());
}
}

View File

@@ -16,6 +16,7 @@
#include "trace.h"
#include "new_utilities.h"
#include "Generated Files/resource.h"
#include "RuntimeRegistration.h"
// Note: Settings are managed via Settings and UI Settings
class NewModule : public PowertoyModuleIface
@@ -93,8 +94,16 @@ public:
// Log telemetry
Trace::EventToggleOnOff(true);
newplus::utilities::register_msix_package();
if (package::IsWin11OrGreater())
{
newplus::utilities::register_msix_package();
}
else
{
#if defined(ENABLE_REGISTRATION) || defined(NDEBUG)
NewPlusRuntimeRegistration::EnsureRegisteredWin10();
#endif
}
powertoy_new_enabled = true;
}
@@ -141,6 +150,13 @@ private:
{
Trace::EventToggleOnOff(false);
}
if (!package::IsWin11OrGreater())
{
#if defined(ENABLE_REGISTRATION) || defined(NDEBUG)
NewPlusRuntimeRegistration::Unregister();
Logger::info(L"New+ context menu unregistered (Win10)");
#endif
}
powertoy_new_enabled = false;
}

View File

@@ -12,6 +12,6 @@
<PackageVersion Include="Microsoft.WindowsAppSDK" Version="1.7.250513003" />
<PackageVersion Include="Shmuelie.WinRTServer" Version="2.1.1" />
<PackageVersion Include="StyleCop.Analyzers" Version="1.2.0-beta.556" />
<PackageVersion Include="System.Text.Json" Version="9.0.7" />
<PackageVersion Include="System.Text.Json" Version="9.0.8" />
</ItemGroup>
</Project>

View File

@@ -9,11 +9,11 @@ using Windows.AI.Actions.Hosting;
namespace Microsoft.CmdPal.Ext.Indexer.Commands;
internal sealed partial class ExecuteActionCommand : InvokableCommand
public sealed partial class ExecuteActionCommand : InvokableCommand
{
private readonly ActionInstance actionInstance;
internal ExecuteActionCommand(ActionInstance actionInstance)
public ExecuteActionCommand(ActionInstance actionInstance)
{
this.actionInstance = actionInstance;
this.Name = actionInstance.DisplayInfo.Description;

View File

@@ -6,28 +6,29 @@ using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using ManagedCommon;
using Microsoft.CmdPal.Ext.Indexer.Data;
using Microsoft.CmdPal.Ext.Indexer.Properties;
using Microsoft.CmdPal.Common.Properties;
using Microsoft.CommandPalette.Extensions.Toolkit;
namespace Microsoft.CmdPal.Ext.Indexer.Commands;
namespace Microsoft.CmdPal.Common.Commands;
internal sealed partial class OpenInConsoleCommand : InvokableCommand
public partial class OpenInConsoleCommand : InvokableCommand
{
private readonly IndexerItem _item;
internal static IconInfo OpenInConsoleIcon { get; } = new("\uE756");
internal OpenInConsoleCommand(IndexerItem item)
private readonly string _path;
public OpenInConsoleCommand(string fullPath)
{
this._item = item;
this._path = fullPath;
this.Name = Resources.Indexer_Command_OpenPathInConsole;
this.Icon = new IconInfo("\uE756");
this.Icon = OpenInConsoleIcon;
}
public override CommandResult Invoke()
{
using (var process = new Process())
{
process.StartInfo.WorkingDirectory = Path.GetDirectoryName(_item.FullPath);
process.StartInfo.WorkingDirectory = Path.GetDirectoryName(_path);
process.StartInfo.FileName = "cmd.exe";
try
@@ -36,10 +37,10 @@ internal sealed partial class OpenInConsoleCommand : InvokableCommand
}
catch (Win32Exception ex)
{
Logger.LogError($"Unable to open {_item.FullPath}", ex);
Logger.LogError($"Unable to open '{_path}'", ex);
}
}
return CommandResult.GoHome();
return CommandResult.Dismiss();
}
}

View File

@@ -6,17 +6,17 @@ using System;
using System.Runtime.InteropServices;
using ManagedCommon;
using ManagedCsWin32;
using Microsoft.CmdPal.Ext.Indexer.Data;
using Microsoft.CmdPal.Ext.Indexer.Indexer.Utils;
using Microsoft.CmdPal.Ext.Indexer.Properties;
using Microsoft.CmdPal.Common.Properties;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Windows.Win32.UI.WindowsAndMessaging;
namespace Microsoft.CmdPal.Ext.Indexer.Commands;
namespace Microsoft.CmdPal.Common.Commands;
internal sealed partial class OpenPropertiesCommand : InvokableCommand
public partial class OpenPropertiesCommand : InvokableCommand
{
private readonly IndexerItem _item;
internal static IconInfo OpenPropertiesIcon { get; } = new("\uE90F");
private readonly string _path;
private static unsafe bool ShowFileProperties(string filename)
{
@@ -31,7 +31,7 @@ internal sealed partial class OpenPropertiesCommand : InvokableCommand
LpVerb = propertiesPtr,
LpFile = filenamePtr,
Show = (int)SHOW_WINDOW_CMD.SW_SHOW,
FMask = NativeHelpers.SEEMASKINVOKEIDLIST,
FMask = global::Windows.Win32.PInvoke.SEE_MASK_INVOKEIDLIST,
};
return Shell32.ShellExecuteEx(ref info);
@@ -43,24 +43,24 @@ internal sealed partial class OpenPropertiesCommand : InvokableCommand
}
}
internal OpenPropertiesCommand(IndexerItem item)
public OpenPropertiesCommand(string fullPath)
{
this._item = item;
this._path = fullPath;
this.Name = Resources.Indexer_Command_OpenProperties;
this.Icon = new IconInfo("\uE90F");
this.Icon = OpenPropertiesIcon;
}
public override CommandResult Invoke()
{
try
{
ShowFileProperties(_item.FullPath);
ShowFileProperties(_path);
}
catch (Exception ex)
{
Logger.LogError("Error showing file properties: ", ex);
}
return CommandResult.GoHome();
return CommandResult.Dismiss();
}
}

View File

@@ -4,17 +4,17 @@
using System.Runtime.InteropServices;
using ManagedCsWin32;
using Microsoft.CmdPal.Ext.Indexer.Data;
using Microsoft.CmdPal.Ext.Indexer.Indexer.Utils;
using Microsoft.CmdPal.Ext.Indexer.Properties;
using Microsoft.CmdPal.Common.Properties;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Windows.Win32.UI.WindowsAndMessaging;
namespace Microsoft.CmdPal.Ext.Indexer.Commands;
namespace Microsoft.CmdPal.Common.Commands;
internal sealed partial class OpenWithCommand : InvokableCommand
public partial class OpenWithCommand : InvokableCommand
{
private readonly IndexerItem _item;
internal static IconInfo OpenWithIcon { get; } = new("\uE7AC");
private readonly string _path;
private static unsafe bool OpenWith(string filename)
{
@@ -29,7 +29,7 @@ internal sealed partial class OpenWithCommand : InvokableCommand
LpVerb = verbPtr,
LpFile = filenamePtr,
Show = (int)SHOW_WINDOW_CMD.SW_SHOWNORMAL,
FMask = NativeHelpers.SEEMASKINVOKEIDLIST,
FMask = global::Windows.Win32.PInvoke.SEE_MASK_INVOKEIDLIST,
};
return Shell32.ShellExecuteEx(ref info);
@@ -41,16 +41,16 @@ internal sealed partial class OpenWithCommand : InvokableCommand
}
}
internal OpenWithCommand(IndexerItem item)
public OpenWithCommand(string fullPath)
{
this._item = item;
this._path = fullPath;
this.Name = Resources.Indexer_Command_OpenWith;
this.Icon = new IconInfo("\uE7AC");
this.Icon = OpenWithIcon;
}
public override CommandResult Invoke()
{
OpenWith(_item.FullPath);
OpenWith(_path);
return CommandResult.GoHome();
}

View File

@@ -0,0 +1,46 @@
// 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;
namespace Microsoft.CmdPal.Common.Helpers;
/// <summary>
/// Thread-safe boolean implementation using atomic operations
/// </summary>
public struct InterlockedBoolean(bool initialValue = false)
{
private int _value = initialValue ? 1 : 0;
/// <summary>
/// Gets or sets the boolean value atomically
/// </summary>
public bool Value
{
get => Volatile.Read(ref _value) == 1;
set => Interlocked.Exchange(ref _value, value ? 1 : 0);
}
/// <summary>
/// Atomically sets the value to true
/// </summary>
/// <returns>True if the value was previously false, false if it was already true</returns>
public bool Set()
{
return Interlocked.Exchange(ref _value, 1) == 0;
}
/// <summary>
/// Atomically sets the value to false
/// </summary>
/// <returns>True if the value was previously true, false if it was already false</returns>
public bool Clear()
{
return Interlocked.Exchange(ref _value, 0) == 1;
}
public override int GetHashCode() => Value.GetHashCode();
public override string ToString() => Value.ToString();
}

View File

@@ -0,0 +1,139 @@
// 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.Threading;
using System.Threading.Tasks;
namespace Microsoft.CmdPal.Common.Helpers;
/// <summary>
/// An async gate that ensures only one operation runs at a time.
/// If ExecuteAsync is called while already executing, it cancels the current execution
/// and starts the operation again (superseding behavior).
/// </summary>
public class SupersedingAsyncGate : IDisposable
{
private readonly Func<CancellationToken, Task> _action;
private readonly Lock _lock = new();
private int _callId;
private TaskCompletionSource<bool>? _currentTcs;
private CancellationTokenSource? _currentCancellationSource;
private Task? _executingTask;
public SupersedingAsyncGate(Func<CancellationToken, Task> action)
{
ArgumentNullException.ThrowIfNull(action);
_action = action;
}
/// <summary>
/// Executes the configured action. If another execution is running, this call will
/// cancel the current execution and restart the operation.
/// </summary>
/// <param name="cancellationToken">Optional external cancellation token</param>
public async Task ExecuteAsync(CancellationToken cancellationToken = default)
{
TaskCompletionSource<bool> tcs;
lock (_lock)
{
_currentCancellationSource?.Cancel();
_currentTcs?.TrySetException(new OperationCanceledException("Superseded by newer call"));
tcs = new();
_currentTcs = tcs;
_callId++;
var shouldStartExecution = _executingTask is null;
if (shouldStartExecution)
{
_executingTask = Task.Run(ExecuteLoop, CancellationToken.None);
}
}
await using var ctr = cancellationToken.Register(() => tcs.TrySetCanceled(cancellationToken));
await tcs.Task;
}
private async Task ExecuteLoop()
{
try
{
while (true)
{
TaskCompletionSource<bool>? currentTcs;
CancellationTokenSource? currentCts;
int currentCallId;
lock (_lock)
{
currentTcs = _currentTcs;
currentCallId = _callId;
if (currentTcs is null)
{
break;
}
_currentCancellationSource?.Dispose();
_currentCancellationSource = new();
currentCts = _currentCancellationSource;
}
try
{
await _action(currentCts.Token);
CompleteIfCurrent(currentTcs, currentCallId, static t => t.TrySetResult(true));
}
catch (OperationCanceledException)
{
CompleteIfCurrent(currentTcs, currentCallId, tcs => tcs.TrySetCanceled(currentCts.Token));
}
catch (Exception ex)
{
CompleteIfCurrent(currentTcs, currentCallId, tcs => tcs.TrySetException(ex));
}
}
}
finally
{
lock (_lock)
{
_currentTcs = null;
_currentCancellationSource?.Dispose();
_currentCancellationSource = null;
_executingTask = null;
}
}
}
private void CompleteIfCurrent(
TaskCompletionSource<bool> candidate,
int id,
Action<TaskCompletionSource<bool>> complete)
{
lock (_lock)
{
if (_currentTcs == candidate && _callId == id)
{
complete(candidate);
_currentTcs = null;
}
}
}
public void Dispose()
{
lock (_lock)
{
_currentCancellationSource?.Cancel();
_currentCancellationSource?.Dispose();
_currentTcs?.TrySetException(new ObjectDisposedException(nameof(SupersedingAsyncGate)));
_currentTcs = null;
}
GC.SuppressFinalize(this);
}
}

View File

@@ -28,7 +28,24 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\common\ManagedCommon\ManagedCommon.csproj" />
<ProjectReference Include="..\..\..\common\ManagedCsWin32\ManagedCsWin32.csproj" />
<ProjectReference Include="..\extensionsdk\Microsoft.CommandPalette.Extensions.Toolkit\Microsoft.CommandPalette.Extensions.Toolkit.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Update="Properties\Resources.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
</Project>

View File

@@ -9,3 +9,7 @@ GetWindowRect
GetMonitorInfo
SetWindowPos
MonitorFromWindow
SHOW_WINDOW_CMD
ShellExecuteEx
SEE_MASK_INVOKEIDLIST

View File

@@ -0,0 +1,99 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace Microsoft.CmdPal.Common.Properties {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.CmdPal.Common.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized string similar to Open path in console.
/// </summary>
internal static string Indexer_Command_OpenPathInConsole {
get {
return ResourceManager.GetString("Indexer_Command_OpenPathInConsole", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Properties.
/// </summary>
internal static string Indexer_Command_OpenProperties {
get {
return ResourceManager.GetString("Indexer_Command_OpenProperties", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Open with.
/// </summary>
internal static string Indexer_Command_OpenWith {
get {
return ResourceManager.GetString("Indexer_Command_OpenWith", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Show in folder.
/// </summary>
internal static string Indexer_Command_ShowInFolder {
get {
return ResourceManager.GetString("Indexer_Command_ShowInFolder", resourceCulture);
}
}
}
}

View File

@@ -0,0 +1,132 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Indexer_Command_OpenPathInConsole" xml:space="preserve">
<value>Open path in console</value>
</data>
<data name="Indexer_Command_OpenProperties" xml:space="preserve">
<value>Properties</value>
</data>
<data name="Indexer_Command_OpenWith" xml:space="preserve">
<value>Open with</value>
</data>
<data name="Indexer_Command_ShowInFolder" xml:space="preserve">
<value>Show in folder</value>
</data>
</root>

View File

@@ -2,11 +2,14 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Diagnostics.CodeAnalysis;
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
namespace Microsoft.CmdPal.Core.ViewModels;
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]
public partial class CommandContextItemViewModel(ICommandContextItem contextItem, WeakReference<IPageContext> context) : CommandItemViewModel(new(contextItem), context), IContextItemViewModel
{
private readonly KeyChord nullKeyChord = new(0, 0, 0);

View File

@@ -2,6 +2,7 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Diagnostics.CodeAnalysis;
using Microsoft.CmdPal.Core.ViewModels.Messages;
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
@@ -9,6 +10,7 @@ using Microsoft.CommandPalette.Extensions.Toolkit;
namespace Microsoft.CmdPal.Core.ViewModels;
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]
public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBarContext
{
public ExtensionObject<ICommandItem> Model => _commandItemModel;
@@ -313,6 +315,10 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
Command = new(model.Command, PageContext);
Command.InitializeProperties();
// Extensions based on Command Palette SDK < 0.3 CommandItem class won't notify when Title changes because Command
// or Command.Name change. This is a workaround to ensure that the Title is always up-to-date for extensions with old SDK.
_itemTitle = model.Title;
UpdateProperty(nameof(Name));
UpdateProperty(nameof(Title));
UpdateProperty(nameof(Icon));
@@ -338,7 +344,7 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
var newContextMenu = more
.Select(item =>
{
if (item is CommandContextItem contextItem)
if (item is ICommandContextItem contextItem)
{
return new CommandContextItemViewModel(contextItem, PageContext) as IContextItemViewModel;
}
@@ -385,6 +391,14 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
switch (propertyName)
{
case nameof(Command.Name):
// Extensions based on Command Palette SDK < 0.3 CommandItem class won't notify when Title changes because Command
// or Command.Name change. This is a workaround to ensure that the Title is always up-to-date for extensions with old SDK.
var model = _commandItemModel.Unsafe;
if (model != null)
{
_itemTitle = model.Title;
}
UpdateProperty(nameof(Title));
UpdateProperty(nameof(Name));
break;

View File

@@ -14,7 +14,7 @@ using Microsoft.CommandPalette.Extensions.Toolkit;
namespace Microsoft.CmdPal.Core.ViewModels;
public partial class ContentPageViewModel : PageViewModel, ICommandBarContext
public abstract partial class ContentPageViewModel : PageViewModel, ICommandBarContext
{
private readonly ExtensionObject<IContentPage> _model;
@@ -113,7 +113,7 @@ public partial class ContentPageViewModel : PageViewModel, ICommandBarContext
.ToList()
.Select<IContextItem, IContextItemViewModel>(item =>
{
if (item is CommandContextItem contextItem)
if (item is ICommandContextItem contextItem)
{
return new CommandContextItemViewModel(contextItem, PageContext);
}
@@ -172,7 +172,7 @@ public partial class ContentPageViewModel : PageViewModel, ICommandBarContext
.ToList()
.Select(item =>
{
if (item is CommandContextItem contextItem)
if (item is ICommandContextItem contextItem)
{
return new CommandContextItemViewModel(contextItem, PageContext) as IContextItemViewModel;
}

View File

@@ -14,8 +14,7 @@ using Windows.System;
namespace Microsoft.CmdPal.Core.ViewModels;
public partial class ContextMenuViewModel : ObservableObject,
IRecipient<UpdateCommandBarMessage>,
IRecipient<OpenContextMenuMessage>
IRecipient<UpdateCommandBarMessage>
{
public ICommandBarContext? SelectedItem
{
@@ -43,7 +42,6 @@ public partial class ContextMenuViewModel : ObservableObject,
public ContextMenuViewModel()
{
WeakReferenceMessenger.Default.Register<UpdateCommandBarMessage>(this);
WeakReferenceMessenger.Default.Register<OpenContextMenuMessage>(this);
}
public void Receive(UpdateCommandBarMessage message)
@@ -51,16 +49,6 @@ public partial class ContextMenuViewModel : ObservableObject,
SelectedItem = message.ViewModel;
}
public void Receive(OpenContextMenuMessage message)
{
FilterOnTop = message.ContextMenuFilterLocation == ContextMenuFilterLocation.Top;
ResetContextMenu();
OnPropertyChanging(nameof(FilterOnTop));
OnPropertyChanged(nameof(FilterOnTop));
}
public void UpdateContextItems()
{
if (SelectedItem != null)
@@ -192,7 +180,7 @@ public partial class ContextMenuViewModel : ObservableObject,
ListHelpers.InPlaceUpdateList(FilteredItems, [.. CurrentContextMenu!]);
}
private void ResetContextMenu()
public void ResetContextMenu()
{
while (ContextMenuStack.Count > 1)
{

View File

@@ -4,12 +4,14 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Microsoft.CmdPal.Core.ViewModels;
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]
public interface IContextItemViewModel
{
}

View File

@@ -6,6 +6,7 @@ using System.Collections.ObjectModel;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.CmdPal.Common.Helpers;
using Microsoft.CmdPal.Core.ViewModels.Messages;
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
@@ -31,7 +32,7 @@ public partial class ListViewModel : PageViewModel, IDisposable
private readonly Lock _listLock = new();
private bool _isLoading;
private InterlockedBoolean _isLoading;
private bool _isFetching;
public event TypedEventHandler<ListViewModel, object>? ItemsUpdated;
@@ -121,7 +122,7 @@ public partial class ListViewModel : PageViewModel, IDisposable
ItemsUpdated?.Invoke(this, EventArgs.Empty);
UpdateEmptyContent();
_isLoading = false;
_isLoading.Clear();
}
}
@@ -221,7 +222,7 @@ public partial class ListViewModel : PageViewModel, IDisposable
}
ItemsUpdated?.Invoke(this, EventArgs.Empty);
_isLoading = false;
_isLoading.Clear();
});
}
@@ -469,21 +470,38 @@ public partial class ListViewModel : PageViewModel, IDisposable
return;
}
if (model.HasMoreItems && !_isLoading)
if (!_isLoading.Set())
{
_isLoading = true;
_ = Task.Run(() =>
return;
// NOTE: May miss newly available items until next scroll if model
// state changes between our check and this reset
}
_ = Task.Run(() =>
{
// Execute all COM calls on background thread to avoid reentrancy issues with UI
// with the UI thread when COM starts inner message pump
try
{
try
if (model.HasMoreItems)
{
model.LoadMore();
// _isLoading flag will be set as a result of LoadMore,
// which must raise ItemsChanged to end the loading.
}
catch (Exception ex)
else
{
ShowException(ex, model.Name);
_isLoading.Clear();
}
});
}
}
catch (Exception ex)
{
_isLoading.Clear();
ShowException(ex, model.Name);
}
});
}
protected override void FetchProperty(string propertyName)

View File

@@ -45,16 +45,4 @@
</Compile>
</ItemGroup>
<!-- Just mark it as AOT compatible. Do not publish with AOT now. We need fully test before we really publish it as AOT enabled-->
<!--<PropertyGroup>
<SelfContained>true</SelfContained>
<WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>
<PublishTrimmed>true</PublishTrimmed>
<PublishSingleFile>true</PublishSingleFile>
--><!-- <DisableRuntimeMarshalling>true</DisableRuntimeMarshalling> --><!--
<PublishAot>true</PublishAot>
<EnableMsixTooling>true</EnableMsixTooling>
</PropertyGroup>-->
</Project>

View File

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

View File

@@ -2,11 +2,13 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Diagnostics.CodeAnalysis;
using Microsoft.CmdPal.Core.ViewModels;
using Microsoft.CommandPalette.Extensions;
namespace Microsoft.CmdPal.Core.ViewModels;
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]
public partial class SeparatorContextItemViewModel() : IContextItemViewModel, ISeparatorContextItem
{
}

View File

@@ -13,7 +13,8 @@ using Microsoft.CommandPalette.Extensions;
namespace Microsoft.CmdPal.Core.ViewModels;
public partial class ShellViewModel : ObservableObject,
IRecipient<PerformCommandMessage>
IRecipient<PerformCommandMessage>,
IRecipient<HandleCommandResultMessage>
{
private readonly IRootPageService _rootPageService;
private readonly IAppHostService _appHostService;
@@ -77,6 +78,7 @@ public partial class ShellViewModel : ObservableObject,
// Register to receive messages
WeakReferenceMessenger.Default.Register<PerformCommandMessage>(this);
WeakReferenceMessenger.Default.Register<HandleCommandResultMessage>(this);
}
[RelayCommand]
@@ -358,6 +360,11 @@ public partial class ShellViewModel : ObservableObject,
WeakReferenceMessenger.Default.Send<GoBackMessage>(new(withAnimation, focusSearch));
}
public void Receive(HandleCommandResultMessage message)
{
UnsafeHandleCommandResult(message.Result.Unsafe);
}
private void OnUIThread(Action action)
{
_ = Task.Factory.StartNew(

View File

@@ -3,11 +3,11 @@
// See the LICENSE file in the project root for more information.
using ManagedCommon;
using Microsoft.CmdPal.Core.ViewModels;
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CmdPal.UI.ViewModels;
using Microsoft.CommandPalette.Extensions;
namespace Microsoft.CmdPal.Core.ViewModels;
namespace Microsoft.CmdPal.UI.ViewModels;
public partial class CommandSettingsViewModel(ICommandSettings? _unsafeSettings, CommandProviderWrapper provider, TaskScheduler mainThread)
{
@@ -29,9 +29,9 @@ public partial class CommandSettingsViewModel(ICommandSettings? _unsafeSettings,
return;
}
if (model.SettingsPage is IContentPage page)
if (model.SettingsPage != null)
{
SettingsPage = new(page, mainThread, provider.ExtensionHost);
SettingsPage = new CommandPaletteContentPageViewModel(model.SettingsPage, mainThread, provider.ExtensionHost);
SettingsPage.InitializeProperties();
}
}

View File

@@ -6,8 +6,10 @@ using System.Collections.Immutable;
using System.Collections.Specialized;
using CommunityToolkit.Mvvm.Messaging;
using ManagedCommon;
using Microsoft.CmdPal.Common.Helpers;
using Microsoft.CmdPal.Core.ViewModels.Messages;
using Microsoft.CmdPal.Ext.Apps;
using Microsoft.CmdPal.UI.ViewModels.Messages;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Microsoft.Extensions.DependencyInjection;
@@ -29,9 +31,13 @@ public partial class MainListPage : DynamicListPage,
private bool _includeApps;
private bool _filteredItemsIncludesApps;
private InterlockedBoolean _refreshRunning;
private InterlockedBoolean _refreshRequested;
public MainListPage(IServiceProvider serviceProvider)
{
Icon = IconHelpers.FromRelativePath("Assets\\StoreLogo.scale-200.png");
PlaceholderText = Properties.Resources.builtin_main_list_page_searchbar_placeholder;
_serviceProvider = serviceProvider;
_tlcManager = _serviceProvider.GetService<TopLevelCommandManager>()!;
@@ -55,6 +61,7 @@ public partial class MainListPage : DynamicListPage,
var settings = _serviceProvider.GetService<SettingsModel>()!;
settings.SettingsChanged += SettingsChangedHandler;
HotReloadSettings(settings);
_includeApps = _tlcManager.IsProviderActive(AllAppsCommandProvider.WellKnownId);
IsLoading = true;
}
@@ -82,18 +89,47 @@ public partial class MainListPage : DynamicListPage,
private void ReapplySearchInBackground()
{
_ = Task.Run(() =>
_refreshRequested.Set();
if (!_refreshRunning.Set())
{
try
return;
}
_ = Task.Run(RunRefreshLoop);
}
private void RunRefreshLoop()
{
try
{
do
{
_refreshRequested.Clear();
lock (_tlcManager.TopLevelCommands)
{
if (_filteredItemsIncludesApps == _includeApps)
{
break;
}
}
var currentSearchText = SearchText;
UpdateSearchText(currentSearchText, currentSearchText);
}
catch (Exception e)
while (_refreshRequested.Value);
}
catch (Exception e)
{
Logger.LogError("Failed to reload search", e);
}
finally
{
_refreshRunning.Clear();
if (_refreshRequested.Value && _refreshRunning.Set())
{
Logger.LogError("Failed to reload search", e);
_ = Task.Run(RunRefreshLoop);
}
});
}
}
public override IListItem[] GetItems()
@@ -125,6 +161,15 @@ public partial class MainListPage : DynamicListPage,
var aliases = _serviceProvider.GetService<AliasManager>()!;
if (aliases.CheckAlias(newSearch))
{
if (_filteredItemsIncludesApps != _includeApps)
{
lock (_tlcManager.TopLevelCommands)
{
_filteredItemsIncludesApps = _includeApps;
_filteredItems = null;
}
}
return;
}
}
@@ -137,6 +182,7 @@ public partial class MainListPage : DynamicListPage,
// Cleared out the filter text? easy. Reset _filteredItems, and bail out.
if (string.IsNullOrEmpty(newSearch))
{
_filteredItemsIncludesApps = _includeApps;
_filteredItems = null;
RaiseItemsChanged(commands.Count);
return;
@@ -164,6 +210,11 @@ public partial class MainListPage : DynamicListPage,
if (_includeApps)
{
IEnumerable<IListItem> apps = AllAppsCommandProvider.Page.GetItems();
var appIds = apps.Select(app => app.Command.Id).ToArray();
// Remove any top level pinned apps and use the apps from AllAppsCommandProvider.Page.GetItems()
// since they contain details.
_filteredItems = _filteredItems.Where(item => item.Command is not AppCommand);
_filteredItems = _filteredItems.Concat(apps);
}
}

View File

@@ -3,7 +3,7 @@
// See the LICENSE file in the project root for more information.
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.CmdPal.Core.ViewModels.Messages;
using Microsoft.CmdPal.UI.Messages;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;

View File

@@ -3,7 +3,7 @@
// See the LICENSE file in the project root for more information.
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.CmdPal.Core.ViewModels.Messages;
using Microsoft.CmdPal.UI.ViewModels.Messages;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;

View File

@@ -4,6 +4,7 @@
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.CmdPal.Core.ViewModels.Messages;
using Microsoft.CmdPal.UI.ViewModels.Messages;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;

View File

@@ -98,35 +98,107 @@ public partial class ContentFormViewModel(IFormContent _form, WeakReference<IPag
public void HandleSubmit(IAdaptiveActionElement action, JsonObject inputs)
{
if (action is AdaptiveOpenUrlAction openUrlAction)
{
WeakReferenceMessenger.Default.Send<LaunchUriMessage>(new(openUrlAction.Url));
return;
}
// BODGY circa GH #40979
// Usually, you're supposed to try to cast the action to a specific
// type, and use those objects to get the data you need.
// However, there's something weird with AdaptiveCards and the way it
// works when we consume it when built in Release, with AOT (and
// trimming) enabled. Any sort of `action.As<IAdaptiveSubmitAction>()`
// or similar will throw a System.InvalidCastException.
//
// Instead we have this horror show.
//
// The `action.ToJson()` blob ACTUALLY CONTAINS THE `type` field, which
// we can use to determine what kind of action it is. Then we can parse
// the JSON manually based on the type.
var actionJson = action.ToJson();
if (action is AdaptiveSubmitAction or AdaptiveExecuteAction)
if (actionJson.TryGetValue("type", out var actionTypeValue))
{
// Get the data and inputs
var dataString = (action as AdaptiveSubmitAction)?.DataJson.Stringify() ?? string.Empty;
var inputString = inputs.Stringify();
var actionTypeString = actionTypeValue.GetString();
Logger.LogTrace($"atString={actionTypeString}");
_ = Task.Run(() =>
var actionType = actionTypeString switch
{
try
{
var model = _formModel.Unsafe!;
if (model != null)
"Action.Submit" => ActionType.Submit,
"Action.Execute" => ActionType.Execute,
"Action.OpenUrl" => ActionType.OpenUrl,
_ => ActionType.Unsupported,
};
Logger.LogDebug($"{actionTypeString}->{actionType}");
switch (actionType)
{
case ActionType.OpenUrl:
{
var result = model.SubmitForm(inputString, dataString);
WeakReferenceMessenger.Default.Send<HandleCommandResultMessage>(new(new(result)));
HandleOpenUrlAction(action, actionJson);
}
}
catch (Exception ex)
{
ShowException(ex);
}
});
break;
case ActionType.Submit:
case ActionType.Execute:
{
HandleSubmitAction(action, actionJson, inputs);
}
break;
default:
Logger.LogError($"{actionType} was an unexpected action `type`");
break;
}
}
else
{
Logger.LogError($"actionJson.TryGetValue(type) failed");
}
}
private void HandleOpenUrlAction(IAdaptiveActionElement action, JsonObject actionJson)
{
if (actionJson.TryGetValue("url", out var actionUrlValue))
{
var actionUrl = actionUrlValue.GetString() ?? string.Empty;
if (Uri.TryCreate(actionUrl, default(UriCreationOptions), out var uri))
{
WeakReferenceMessenger.Default.Send<LaunchUriMessage>(new(uri));
}
else
{
Logger.LogError($"Failed to produce URI for {actionUrlValue}");
}
}
}
private void HandleSubmitAction(
IAdaptiveActionElement action,
JsonObject actionJson,
JsonObject inputs)
{
var dataString = string.Empty;
if (actionJson.TryGetValue("data", out var actionDataValue))
{
dataString = actionDataValue.Stringify() ?? string.Empty;
}
var inputString = inputs.Stringify();
_ = Task.Run(() =>
{
try
{
var model = _formModel.Unsafe!;
if (model != null)
{
var result = model.SubmitForm(inputString, dataString);
Logger.LogDebug($"SubmitForm() returned {result}");
WeakReferenceMessenger.Default.Send<HandleCommandResultMessage>(new(new(result)));
}
}
catch (Exception ex)
{
ShowException(ex);
}
});
}
private static readonly string ErrorCardJson = """

View File

@@ -2,7 +2,7 @@
// 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.Core.ViewModels.Messages;
namespace Microsoft.CmdPal.UI.Messages;
public record OpenSettingsMessage()
{

View File

@@ -2,7 +2,7 @@
// 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.Core.ViewModels.Messages;
namespace Microsoft.CmdPal.UI.ViewModels.Messages;
/// <summary>
/// Message which closes the application. Used by <see cref="QuitCommand"/> via <see cref="BuiltInsCommandProvider"/>.

View File

@@ -2,7 +2,7 @@
// 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.Core.ViewModels.Messages;
namespace Microsoft.CmdPal.UI.ViewModels.Messages;
public record ReloadCommandsMessage()
{

View File

@@ -2,7 +2,7 @@
// 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.Core.ViewModels.Messages;
namespace Microsoft.CmdPal.UI.ViewModels.Messages;
public record UpdateFallbackItemsMessage()
{

View File

@@ -2,6 +2,7 @@
// 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.CommandPalette.Extensions;
using Windows.ApplicationModel;
@@ -287,9 +288,17 @@ public partial class ExtensionService : IExtensionService, IDisposable
var installedExtensions = await GetInstalledExtensionsAsync();
foreach (var installedExtension in installedExtensions)
{
if (installedExtension.IsRunning())
Logger.LogDebug($"Signaling dispose to {installedExtension.ExtensionUniqueId}");
try
{
installedExtension.SignalDispose();
if (installedExtension.IsRunning())
{
installedExtension.SignalDispose();
}
}
catch (Exception ex)
{
Logger.LogError($"Failed to send dispose signal to extension {installedExtension.ExtensionUniqueId}", ex);
}
}
}

View File

@@ -321,6 +321,15 @@ namespace Microsoft.CmdPal.UI.ViewModels.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to Search for apps, files and commands....
/// </summary>
public static string builtin_main_list_page_searchbar_placeholder {
get {
return ResourceManager.GetString("builtin_main_list_page_searchbar_placeholder", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Creates a project for a new Command Palette extension.
/// </summary>

View File

@@ -227,4 +227,7 @@
<data name="builtin_disabled_extension" xml:space="preserve">
<value>Disabled</value>
</data>
<data name="builtin_main_list_page_searchbar_placeholder" xml:space="preserve">
<value>Search for apps, files and commands...</value>
</data>
</root>

View File

@@ -7,7 +7,7 @@ using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.CmdPal.Common.Services;
using Microsoft.CmdPal.Core.ViewModels;
using Microsoft.CmdPal.Core.ViewModels.Messages;
using Microsoft.CmdPal.UI.ViewModels.Messages;
using Microsoft.CmdPal.UI.ViewModels.Properties;
using Microsoft.Extensions.DependencyInjection;

View File

@@ -9,9 +9,10 @@ using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using ManagedCommon;
using Microsoft.CmdPal.Common.Helpers;
using Microsoft.CmdPal.Common.Services;
using Microsoft.CmdPal.Core.ViewModels;
using Microsoft.CmdPal.Core.ViewModels.Messages;
using Microsoft.CmdPal.UI.ViewModels.Messages;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Microsoft.Extensions.DependencyInjection;
@@ -20,7 +21,8 @@ namespace Microsoft.CmdPal.UI.ViewModels;
public partial class TopLevelCommandManager : ObservableObject,
IRecipient<ReloadCommandsMessage>,
IPageContext
IPageContext,
IDisposable
{
private readonly IServiceProvider _serviceProvider;
private readonly TaskScheduler _taskScheduler;
@@ -28,6 +30,7 @@ public partial class TopLevelCommandManager : ObservableObject,
private readonly List<CommandProviderWrapper> _builtInCommands = [];
private readonly List<CommandProviderWrapper> _extensionCommandProviders = [];
private readonly Lock _commandProvidersLock = new();
private readonly SupersedingAsyncGate _reloadCommandsGate;
TaskScheduler IPageContext.Scheduler => _taskScheduler;
@@ -36,6 +39,7 @@ public partial class TopLevelCommandManager : ObservableObject,
_serviceProvider = serviceProvider;
_taskScheduler = _serviceProvider.GetService<TaskScheduler>()!;
WeakReferenceMessenger.Default.Register<ReloadCommandsMessage>(this);
_reloadCommandsGate = new(ReloadAllCommandsAsyncCore);
}
public ObservableCollection<TopLevelViewModel> TopLevelCommands { get; set; } = [];
@@ -144,46 +148,10 @@ public partial class TopLevelCommandManager : ObservableObject,
/// <returns>an awaitable task</returns>
private async Task UpdateCommandsForProvider(CommandProviderWrapper sender, IItemsChangedEventArgs args)
{
// Work on a clone of the list, so that we can just do one atomic
// update to the actual observable list at the end
List<TopLevelViewModel> clone = [.. TopLevelCommands];
List<TopLevelViewModel> newItems = [];
var startIndex = -1;
var firstCommand = sender.TopLevelItems[0];
var commandsToRemove = sender.TopLevelItems.Length + sender.FallbackItems.Length;
// Tricky: all Commands from a single provider get added to the
// top-level list all together, in a row. So if we find just the first
// one, we can slice it out and insert the new ones there.
for (var i = 0; i < clone.Count; i++)
{
var wrapper = clone[i];
try
{
var isTheSame = wrapper == firstCommand;
if (isTheSame)
{
startIndex = i;
break;
}
}
catch
{
}
}
WeakReference<IPageContext> weakSelf = new(this);
// Fetch the new items
await sender.LoadTopLevelCommands(_serviceProvider, weakSelf);
var settings = _serviceProvider.GetService<SettingsModel>()!;
foreach (var i in sender.TopLevelItems)
{
newItems.Add(i);
}
List<TopLevelViewModel> newItems = [.. sender.TopLevelItems];
foreach (var i in sender.FallbackItems)
{
if (i.IsEnabled)
@@ -192,25 +160,52 @@ public partial class TopLevelCommandManager : ObservableObject,
}
}
// Slice out the old commands
if (startIndex != -1)
// modify the TopLevelCommands under shared lock; event if we clone it, we don't want
// TopLevelCommands to get modified while we're working on it. Otherwise, we might
// out clone would be stale at the end of this method.
lock (TopLevelCommands)
{
clone.RemoveRange(startIndex, commandsToRemove);
}
else
{
// ... or, just stick them at the end (this is unexpected)
startIndex = clone.Count;
}
// Work on a clone of the list, so that we can just do one atomic
// update to the actual observable list at the end
// TODO: just added a lock around all of this anyway, but keeping the clone
// while looking on some other ways to improve this; can be removed later.
List<TopLevelViewModel> clone = [.. TopLevelCommands];
var startIndex = -1;
// add the new commands into the list at the place we found the old ones
clone.InsertRange(startIndex, newItems);
// Tricky: all Commands from a single provider get added to the
// top-level list all together, in a row. So if we find just the first
// one, we can slice it out and insert the new ones there.
for (var i = 0; i < clone.Count; i++)
{
var wrapper = clone[i];
try
{
if (sender.ProviderId == wrapper.CommandProviderId)
{
startIndex = i;
break;
}
}
catch
{
}
}
// now update the actual observable list with the new contents
ListHelpers.InPlaceUpdateList(TopLevelCommands, clone);
clone.RemoveAll(item => item.CommandProviderId == sender.ProviderId);
clone.InsertRange(startIndex, newItems);
ListHelpers.InPlaceUpdateList(TopLevelCommands, clone);
}
}
public async Task ReloadAllCommandsAsync()
{
// gate ensures that the reload is serialized and if multiple calls
// request a reload, only the first and the last one will be executed.
// this should be superseded with a cancellable version.
await _reloadCommandsGate.ExecuteAsync(CancellationToken.None);
}
private async Task ReloadAllCommandsAsyncCore(CancellationToken cancellationToken)
{
IsLoading = true;
var extensionService = _serviceProvider.GetService<IExtensionService>()!;
@@ -419,4 +414,10 @@ public partial class TopLevelCommandManager : ObservableObject,
|| _extensionCommandProviders.Any(wrapper => wrapper.Id == id && wrapper.IsActive);
}
}
public void Dispose()
{
_reloadCommandsGate.Dispose();
GC.SuppressFinalize(this);
}
}

View File

@@ -8,6 +8,7 @@ using CommunityToolkit.Mvvm.Messaging;
using ManagedCommon;
using Microsoft.CmdPal.Core.ViewModels;
using Microsoft.CmdPal.Core.ViewModels.Messages;
using Microsoft.CmdPal.UI.ViewModels.Messages;
using Microsoft.CmdPal.UI.ViewModels.Settings;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
@@ -47,6 +48,8 @@ public sealed partial class TopLevelViewModel : ObservableObject, IListItem
public CommandItemViewModel ItemViewModel => _commandItemViewModel;
public string CommandProviderId => _commandProviderId;
////// ICommandItem
public string Title => _commandItemViewModel.Title;
@@ -63,9 +66,13 @@ public sealed partial class TopLevelViewModel : ObservableObject, IListItem
{
return item as IContextItem;
}
else if (item is CommandContextItemViewModel commandItem)
{
return commandItem.Model.Unsafe;
}
else
{
return ((CommandContextItemViewModel)item).Model.Unsafe;
return null;
}
}).ToArray();
@@ -347,4 +354,9 @@ public sealed partial class TopLevelViewModel : ObservableObject, IListItem
{
return new PerformCommandMessage(this.CommandViewModel.Model, new Core.ViewModels.Models.ExtensionObject<IListItem>(this));
}
public override string ToString()
{
return $"{nameof(TopLevelViewModel)}: {Id} ({Title}) - display: {DisplayTitle} - fallback: {IsFallback} - enabled: {IsEnabled}";
}
}

View File

@@ -64,6 +64,9 @@ public partial class App : Application
this.InitializeComponent();
// Ensure types used in XAML are preserved for AOT compilation
TypePreservation.PreserveTypes();
NativeEventWaiter.WaitForEventLoop(
"Local\\PowerToysCmdPal-ExitEvent-eb73f6be-3f22-4b36-aee3-62924ba40bfd", () =>
{

View File

@@ -59,8 +59,24 @@
<Setter Property="BorderBrush" Value="{ThemeResource DividerStrokeColorDefaultBrush}" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="CornerRadius" Value="6" />
<Setter Property="MinWidth" Value="20" />
</Style.Setters>
</Style>
<Style x:Key="HotkeyTextBlockStyle" TargetType="TextBlock">
<Setter Property="FontSize" Value="10" />
<Setter Property="CharacterSpacing" Value="4" />
<Setter Property="Foreground" Value="{ThemeResource TextFillColorPrimaryBrush}" />
<Setter Property="HorizontalAlignment" Value="Center" />
<Setter Property="VerticalAlignment" Value="Center" />
</Style>
<Style x:Key="HotkeyFontIconStyle" TargetType="FontIcon">
<Setter Property="FontSize" Value="10" />
<Setter Property="Foreground" Value="{ThemeResource TextFillColorPrimaryBrush}" />
<Setter Property="HorizontalAlignment" Value="Center" />
<Setter Property="VerticalAlignment" Value="Center" />
</Style>
</ResourceDictionary>
</UserControl.Resources>
@@ -76,44 +92,44 @@
<Grid
x:Name="IconRoot"
Margin="8,0,0,0"
Tapped="PageIcon_Tapped"
Margin="3,0,-5,0"
Visibility="{x:Bind CurrentPageViewModel.IsNested, Mode=OneWay}">
<InfoBadge Visibility="{x:Bind CurrentPageViewModel.HasStatusMessage, Mode=OneWay}" Value="{x:Bind CurrentPageViewModel.StatusMessages.Count, Mode=OneWay}" />
<Grid.ContextFlyout>
<Flyout x:Name="StatusMessagesFlyout" Placement="TopEdgeAlignedLeft">
<ItemsRepeater
x:Name="MessagesDropdown"
Margin="-8"
ItemsSource="{x:Bind CurrentPageViewModel.StatusMessages, Mode=OneWay}"
Layout="{StaticResource VerticalStackLayout}">
<ItemsRepeater.ItemTemplate>
<DataTemplate x:DataType="coreViewModels:StatusMessageViewModel">
<StackPanel
Grid.Row="0"
Margin="0"
HorizontalAlignment="Stretch"
VerticalAlignment="Bottom"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
CornerRadius="0">
<InfoBar
CornerRadius="{ThemeResource ControlCornerRadius}"
IsClosable="False"
IsOpen="True"
Message="{x:Bind Message, Mode=OneWay}"
Severity="{x:Bind State, Mode=OneWay, Converter={StaticResource MessageStateToSeverityConverter}}" />
</StackPanel>
</DataTemplate>
</ItemsRepeater.ItemTemplate>
</ItemsRepeater>
</Flyout>
</Grid.ContextFlyout>
<Button
x:Name="StatusMessagesButton"
x:Uid="StatusMessagesButton"
Padding="4"
Style="{StaticResource SubtleButtonStyle}"
Visibility="{x:Bind CurrentPageViewModel.HasStatusMessage, Mode=OneWay}">
<InfoBadge Value="{x:Bind CurrentPageViewModel.StatusMessages.Count, Mode=OneWay}" />
<Button.Flyout>
<Flyout x:Name="StatusMessagesFlyout" Placement="TopEdgeAlignedLeft">
<ItemsRepeater
x:Name="MessagesDropdown"
Margin="-8"
ItemsSource="{x:Bind CurrentPageViewModel.StatusMessages, Mode=OneWay}"
Layout="{StaticResource VerticalStackLayout}">
<ItemsRepeater.ItemTemplate>
<DataTemplate x:DataType="coreViewModels:StatusMessageViewModel">
<StackPanel HorizontalAlignment="Stretch" VerticalAlignment="Bottom">
<InfoBar
CornerRadius="{ThemeResource ControlCornerRadius}"
IsClosable="False"
IsOpen="True"
Message="{x:Bind Message, Mode=OneWay}"
Severity="{x:Bind State, Mode=OneWay, Converter={StaticResource MessageStateToSeverityConverter}}" />
</StackPanel>
</DataTemplate>
</ItemsRepeater.ItemTemplate>
</ItemsRepeater>
</Flyout>
</Button.Flyout>
</Button>
</Grid>
<Button
x:Name="SettingsIconButton"
x:Uid="SettingsButton"
Click="SettingsIcon_Clicked"
Style="{StaticResource SubtleButtonStyle}"
Tapped="SettingsIcon_Tapped"
Visibility="{x:Bind CurrentPageViewModel.IsNested, Mode=OneWay, Converter={StaticResource BoolToInvertedVisibilityConverter}}">
<StackPanel Orientation="Horizontal" Spacing="8">
<FontIcon
@@ -146,8 +162,8 @@
x:Load="{x:Bind IsLoaded, Mode=OneWay}"
AutomationProperties.Name="{x:Bind ViewModel.PrimaryCommand.Name, Mode=OneWay}"
Background="Transparent"
Click="PrimaryButton_Clicked"
Style="{StaticResource SubtleButtonStyle}"
Tapped="PrimaryButton_Tapped"
Visibility="{x:Bind ViewModel.HasPrimaryCommand, Mode=OneWay}">
<StackPanel Orientation="Horizontal" Spacing="8">
<TextBlock
@@ -155,12 +171,7 @@
Style="{StaticResource CaptionTextBlockStyle}"
Text="{x:Bind ViewModel.PrimaryCommand.Name, Mode=OneWay}" />
<Border Style="{StaticResource HotkeyStyle}">
<FontIcon
HorizontalAlignment="Left"
VerticalAlignment="Center"
FontSize="10"
Foreground="{ThemeResource TextFillColorPrimaryBrush}"
Glyph="&#xE751;" />
<FontIcon Glyph="&#xE751;" Style="{StaticResource HotkeyFontIconStyle}" />
</Border>
</StackPanel>
</Button>
@@ -169,8 +180,8 @@
Padding="6,4,4,4"
x:Load="{x:Bind IsLoaded, Mode=OneWay}"
AutomationProperties.Name="{x:Bind ViewModel.SecondaryCommand.Name, Mode=OneWay}"
Click="SecondaryButton_Clicked"
Style="{StaticResource SubtleButtonStyle}"
Tapped="SecondaryButton_Tapped"
Visibility="{x:Bind ViewModel.HasSecondaryCommand, Mode=OneWay}">
<StackPanel Orientation="Horizontal" Spacing="8">
<TextBlock
@@ -179,19 +190,10 @@
Text="{x:Bind ViewModel.SecondaryCommand.Name, Mode=OneWay}" />
<StackPanel Orientation="Horizontal" Spacing="4">
<Border Padding="4,2,4,2" Style="{StaticResource HotkeyStyle}">
<TextBlock
CharacterSpacing="4"
FontSize="10"
Foreground="{ThemeResource TextFillColorPrimaryBrush}"
Text="Ctrl" />
<TextBlock Style="{StaticResource HotkeyTextBlockStyle}" Text="Ctrl" />
</Border>
<Border Style="{StaticResource HotkeyStyle}">
<FontIcon
HorizontalAlignment="Left"
VerticalAlignment="Center"
FontSize="10"
Foreground="{ThemeResource TextFillColorPrimaryBrush}"
Glyph="&#xE751;" />
<FontIcon Glyph="&#xE751;" Style="{StaticResource HotkeyFontIconStyle}" />
</Border>
</StackPanel>
</StackPanel>
@@ -199,13 +201,26 @@
<Button
x:Name="MoreCommandsButton"
x:Uid="MoreCommandsButton"
Padding="4"
Content="{ui:FontIcon Glyph=&#xE712;,
FontSize=16}"
Padding="6,4,4,4"
Click="MoreCommandsButton_Clicked"
Style="{StaticResource SubtleButtonStyle}"
Tapped="MoreCommandsButton_Tapped"
ToolTipService.ToolTip="Ctrl+K"
Visibility="{x:Bind ViewModel.ShouldShowContextMenu, Mode=OneWay}" />
Visibility="{x:Bind ViewModel.ShouldShowContextMenu, Mode=OneWay}">
<StackPanel Orientation="Horizontal" Spacing="8">
<TextBlock
VerticalAlignment="Center"
Style="{StaticResource CaptionTextBlockStyle}"
Text="More" />
<StackPanel Orientation="Horizontal" Spacing="4">
<Border Padding="4,2,4,2" Style="{StaticResource HotkeyStyle}">
<TextBlock Style="{StaticResource HotkeyTextBlockStyle}" Text="Ctrl" />
</Border>
<Border Padding="4,2,4,2" Style="{StaticResource HotkeyStyle}">
<TextBlock Style="{StaticResource HotkeyTextBlockStyle}" Text="K" />
</Border>
</StackPanel>
</StackPanel>
</Button>
</StackPanel>
</Grid>
</UserControl>

View File

@@ -5,6 +5,7 @@
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.CmdPal.Core.ViewModels;
using Microsoft.CmdPal.Core.ViewModels.Messages;
using Microsoft.CmdPal.UI.Messages;
using Microsoft.CmdPal.UI.Views;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
@@ -113,34 +114,23 @@ public sealed partial class CommandBar : UserControl,
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("CodeQuality", "IDE0051:Remove unused private members", Justification = "VS has a tendency to delete XAML bound methods over-aggressively")]
private void PrimaryButton_Tapped(object sender, TappedRoutedEventArgs e)
private void PrimaryButton_Clicked(object sender, RoutedEventArgs e)
{
ViewModel.InvokePrimaryCommand();
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("CodeQuality", "IDE0051:Remove unused private members", Justification = "VS has a tendency to delete XAML bound methods over-aggressively")]
private void SecondaryButton_Tapped(object sender, TappedRoutedEventArgs e)
private void SecondaryButton_Clicked(object sender, RoutedEventArgs e)
{
ViewModel.InvokeSecondaryCommand();
}
private void PageIcon_Tapped(object sender, TappedRoutedEventArgs e)
{
if (CurrentPageViewModel?.StatusMessages.Count > 0)
{
StatusMessagesFlyout.ShowAt(
placementTarget: IconRoot,
showOptions: new FlyoutShowOptions() { ShowMode = FlyoutShowMode.Standard });
}
}
private void SettingsIcon_Tapped(object sender, TappedRoutedEventArgs e)
private void SettingsIcon_Clicked(object sender, RoutedEventArgs e)
{
WeakReferenceMessenger.Default.Send<OpenSettingsMessage>();
e.Handled = true;
}
private void MoreCommandsButton_Tapped(object sender, TappedRoutedEventArgs e)
private void MoreCommandsButton_Clicked(object sender, RoutedEventArgs e)
{
WeakReferenceMessenger.Default.Send<OpenContextMenuMessage>(new OpenContextMenuMessage(null, null, null, ContextMenuFilterLocation.Bottom));
}

View File

@@ -2,9 +2,7 @@
// 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.Runtime.InteropServices;
using CommunityToolkit.WinUI;
using ManagedCommon;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
@@ -37,14 +35,7 @@ public partial class ContentIcon : FontIcon
{
if (this.FindDescendants().OfType<Grid>().FirstOrDefault() is Grid grid && Content is not null)
{
try
{
grid.Children.Add(Content);
}
catch (COMException ex)
{
Logger.LogError(ex.ToString());
}
grid.Children.Add(Content);
}
}
}

View File

@@ -131,7 +131,7 @@
ItemClick="CommandsDropdown_ItemClick"
ItemTemplateSelector="{StaticResource ContextItemTemplateSelector}"
ItemsSource="{x:Bind ViewModel.FilteredItems, Mode=OneWay}"
KeyDown="CommandsDropdown_KeyDown"
PreviewKeyDown="CommandsDropdown_PreviewKeyDown"
SelectionMode="Single">
<ListView.ItemContainerStyle>
<Style BasedOn="{StaticResource DefaultListViewItemStyle}" TargetType="ListViewItem">

View File

@@ -5,6 +5,7 @@
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.CmdPal.Core.ViewModels;
using Microsoft.CmdPal.Core.ViewModels.Messages;
using Microsoft.CmdPal.UI.Messages;
using Microsoft.UI.Input;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
@@ -38,6 +39,9 @@ public sealed partial class ContextMenu : UserControl,
public void Receive(OpenContextMenuMessage message)
{
ViewModel.FilterOnTop = message.ContextMenuFilterLocation == ContextMenuFilterLocation.Top;
ViewModel.ResetContextMenu();
UpdateUiForStackChange();
}
@@ -80,7 +84,7 @@ public sealed partial class ContextMenu : UserControl,
}
}
private void CommandsDropdown_KeyDown(object sender, KeyRoutedEventArgs e)
private void CommandsDropdown_PreviewKeyDown(object sender, KeyRoutedEventArgs e)
{
if (e.Handled)
{
@@ -170,8 +174,6 @@ public sealed partial class ContextMenu : UserControl,
e.Handled = true;
}
CommandsDropdown_KeyDown(sender, e);
}
private void ContextFilterBox_PreviewKeyDown(object sender, KeyRoutedEventArgs e)
@@ -188,6 +190,8 @@ public sealed partial class ContextMenu : UserControl,
e.Handled = true;
}
CommandsDropdown_PreviewKeyDown(sender, e);
}
private void NavigateUp()

View File

@@ -6,6 +6,7 @@ using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.WinUI;
using Microsoft.CmdPal.Core.ViewModels;
using Microsoft.CmdPal.Core.ViewModels.Messages;
using Microsoft.CmdPal.UI.Messages;
using Microsoft.CmdPal.UI.Views;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Input;
@@ -158,16 +159,6 @@ public sealed partial class SearchBar : UserControl,
CurrentPageViewModel.Filter = FilterBox.Text;
}
}
if (!e.Handled)
{
// The CommandBar is responsible for handling all the item keybindings,
// since the bound context item may need to then show another
// context menu
TryCommandKeybindingMessage msg = new(ctrlPressed, altPressed, shiftPressed, winPressed, e.Key);
WeakReferenceMessenger.Default.Send(msg);
e.Handled = msg.Handled;
}
}
private void FilterBox_PreviewKeyDown(object sender, KeyRoutedEventArgs e)
@@ -255,6 +246,22 @@ public sealed partial class SearchBar : UserControl,
_inSuggestion = false;
_lastText = null;
}
if (!e.Handled)
{
var ctrlPressed = InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.Control).HasFlag(CoreVirtualKeyStates.Down);
var altPressed = InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.Menu).HasFlag(CoreVirtualKeyStates.Down);
var shiftPressed = InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.Shift).HasFlag(CoreVirtualKeyStates.Down);
var winPressed = InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.LeftWindows).HasFlag(CoreVirtualKeyStates.Down) ||
InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.RightWindows).HasFlag(CoreVirtualKeyStates.Down);
// The CommandBar is responsible for handling all the item keybindings,
// since the bound context item may need to then show another
// context menu
TryCommandKeybindingMessage msg = new(ctrlPressed, altPressed, shiftPressed, winPressed, e.Key);
WeakReferenceMessenger.Default.Send(msg);
e.Handled = msg.Handled;
}
}
private void FilterBox_PreviewKeyUp(object sender, KeyRoutedEventArgs e)

View File

@@ -15,7 +15,7 @@
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" />
<TextBlock Grid.Row="0" IsTabStop="True" />
<ItemsControl
x:Name="KeysControl"

View File

@@ -2,6 +2,7 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Diagnostics.CodeAnalysis;
using Microsoft.CmdPal.Core.ViewModels;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
@@ -10,6 +11,7 @@ using Microsoft.UI.Xaml.Data;
namespace Microsoft.CmdPal.UI;
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]
internal sealed partial class ContextItemTemplateSelector : DataTemplateSelector
{
public DataTemplate? Default { get; set; }
@@ -33,9 +35,14 @@ internal sealed partial class ContextItemTemplateSelector : DataTemplateSelector
li.AllowFocusOnInteraction = false;
dataTemplate = Separator;
}
else if (item is CommandContextItemViewModel commandItem)
{
dataTemplate = commandItem.IsCritical ? Critical : Default;
}
else
{
dataTemplate = ((CommandContextItemViewModel)item).IsCritical ? Critical : Default;
// Fallback for unknown types
dataTemplate = Default;
}
}

View File

@@ -113,13 +113,14 @@
<ListView
x:Name="ItemsList"
Padding="0,2,0,0"
ContextCanceled="ItemsList_OnContextCanceled"
ContextRequested="ItemsList_OnContextRequested"
DoubleTapped="ItemsList_DoubleTapped"
IsDoubleTapEnabled="True"
IsItemClickEnabled="True"
ItemClick="ItemsList_ItemClick"
ItemTemplate="{StaticResource ListItemViewModelTemplate}"
ItemsSource="{x:Bind ViewModel.FilteredItems, Mode=OneWay}"
RightTapped="ItemsList_RightTapped"
SelectionChanged="ItemsList_SelectionChanged">
<ListView.ItemContainerTransitions>
<TransitionCollection />

View File

@@ -7,6 +7,7 @@ using CommunityToolkit.Mvvm.Messaging;
using ManagedCommon;
using Microsoft.CmdPal.Core.ViewModels;
using Microsoft.CmdPal.Core.ViewModels.Messages;
using Microsoft.CmdPal.UI.Messages;
using Microsoft.CmdPal.UI.ViewModels;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.UI.Xaml;
@@ -144,6 +145,18 @@ public sealed partial class ListPage : Page,
if (ItemsList.SelectedItem != null)
{
ItemsList.ScrollIntoView(ItemsList.SelectedItem);
// Automation notification for screen readers
var listViewPeer = Microsoft.UI.Xaml.Automation.Peers.ListViewAutomationPeer.CreatePeerForElement(ItemsList);
if (listViewPeer != null && li != null)
{
var notificationText = li.Title;
listViewPeer.RaiseNotificationEvent(
Microsoft.UI.Xaml.Automation.Peers.AutomationNotificationKind.Other,
Microsoft.UI.Xaml.Automation.Peers.AutomationNotificationProcessing.MostRecent,
notificationText,
"CommandPaletteSelectedItemChanged");
}
}
}
@@ -303,30 +316,51 @@ public sealed partial class ListPage : Page,
return null;
}
private void ItemsList_RightTapped(object sender, RightTappedRoutedEventArgs e)
private void ItemsList_OnContextRequested(UIElement sender, ContextRequestedEventArgs e)
{
if (e.OriginalSource is FrameworkElement element &&
element.DataContext is ListItemViewModel item)
var (item, element) = e.OriginalSource switch
{
if (ItemsList.SelectedItem != item)
{
ItemsList.SelectedItem = item;
}
// caused by keyboard shortcut (e.g. Context menu key or Shift+F10)
ListViewItem listViewItem => (ItemsList.ItemFromContainer(listViewItem) as ListItemViewModel, listViewItem),
ViewModel?.UpdateSelectedItemCommand.Execute(item);
// caused by right-click on the ListViewItem
FrameworkElement { DataContext: ListItemViewModel itemViewModel } frameworkElement => (itemViewModel, frameworkElement),
var pos = e.GetPosition(element);
_ => (null, null),
};
_ = DispatcherQueue.TryEnqueue(
() =>
{
WeakReferenceMessenger.Default.Send<OpenContextMenuMessage>(
new OpenContextMenuMessage(
element,
Microsoft.UI.Xaml.Controls.Primitives.FlyoutPlacementMode.BottomEdgeAlignedLeft,
pos,
ContextMenuFilterLocation.Top));
});
if (item == null || element == null)
{
return;
}
if (ItemsList.SelectedItem != item)
{
ItemsList.SelectedItem = item;
}
ViewModel?.UpdateSelectedItemCommand.Execute(item);
if (!e.TryGetPosition(element, out var pos))
{
pos = new(0, element.ActualHeight);
}
_ = DispatcherQueue.TryEnqueue(
() =>
{
WeakReferenceMessenger.Default.Send<OpenContextMenuMessage>(
new OpenContextMenuMessage(
element,
Microsoft.UI.Xaml.Controls.Primitives.FlyoutPlacementMode.BottomEdgeAlignedLeft,
pos,
ContextMenuFilterLocation.Top));
});
e.Handled = true;
}
private void ItemsList_OnContextCanceled(UIElement sender, RoutedEventArgs e)
{
_ = DispatcherQueue.TryEnqueue(() => WeakReferenceMessenger.Default.Send<CloseContextMenuMessage>());
}
}

View File

@@ -34,9 +34,9 @@ public sealed class IconCacheService(DispatcherQueue dispatcherQueue)
{
return await StreamToIconSource(icon.Data.Unsafe!);
}
catch
catch (Exception ex)
{
Debug.WriteLine("Failed to load icon from stream");
Debug.WriteLine("Failed to load icon from stream: " + ex);
}
}
}
@@ -63,17 +63,37 @@ public sealed class IconCacheService(DispatcherQueue dispatcherQueue)
{
// Return the bitmap image via TaskCompletionSource. Using WCT's EnqueueAsync does not suffice here, since if
// we're already on the thread of the DispatcherQueue then it just directly calls the function, with no async involved.
var completionSource = new TaskCompletionSource<BitmapImage>();
dispatcherQueue.TryEnqueue(async () =>
return await TryEnqueueAsync(dispatcherQueue, async () =>
{
using var bitmapStream = await iconStreamRef.OpenReadAsync();
var itemImage = new BitmapImage();
await itemImage.SetSourceAsync(bitmapStream);
completionSource.TrySetResult(itemImage);
return itemImage;
});
}
private static Task<T> TryEnqueueAsync<T>(DispatcherQueue dispatcher, Func<Task<T>> function)
{
var completionSource = new TaskCompletionSource<T>();
var enqueued = dispatcher.TryEnqueue(DispatcherQueuePriority.Normal, async void () =>
{
try
{
var result = await function();
completionSource.SetResult(result);
}
catch (Exception ex)
{
completionSource.SetException(ex);
}
});
var bitmapImage = await completionSource.Task;
if (!enqueued)
{
completionSource.SetException(new InvalidOperationException("Failed to enqueue the operation on the UI dispatcher"));
}
return bitmapImage;
return completionSource.Task;
}
}

View File

@@ -1,429 +0,0 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices.WindowsRuntime;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Media.Imaging;
using Windows.Graphics.Imaging;
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.Graphics.Gdi;
using Windows.Win32.UI.WindowsAndMessaging;
// This is an unfortunate interaction between CsWin32 and CsWinRT.
// The warning is technically correct, but because you can't edit generated
// files, you can't really fix it. Ideally CsWin32 would generate all types as
// partial, but it doesn't do that today
[assembly: SuppressMessage("Usage", "CsWinRT1028:Class is not marked partial", Justification = "Type is not passed across the WinRT ABI", Scope = "type", Target = "Windows.Win32.DeleteObjectSafeHandle")]
[assembly: SuppressMessage("Usage", "CsWinRT1028:Class is not marked partial", Justification = "Type is not passed across the WinRT ABI", Scope = "type", Target = "Windows.Win32.DestroyIconSafeHandle")]
// Relatedly, we added a NativeMethods.json to be able to fix some
// error CA1420: Setting SetLastError to 'true' requires runtime marshalling to be enabled
// errors.
namespace Microsoft.CmdPal.UI.Helpers;
/// <summary>
/// Converts icon paths to IconSource and IconElement objects for WinUI controls.
/// This is a C# port of the C++ IconPathConverter from Microsoft.Terminal.UI.
/// </summary>
public static class IconPathConverter
{
/// <summary>
/// Creates an IconSource for the given path with default settings.
/// </summary>
/// <param name="iconPath">The path to the icon.</param>
/// <returns>An IconSource with its source set, if possible.</returns>
public static IconSource? IconSource(string iconPath, string? fontFamily = null)
{
return IconSource(iconPath, false, fontFamily, 24);
}
/// <summary>
/// Creates an IconSource for the given path.
/// </summary>
/// <param name="iconPath">The path to the icon.</param>
/// <param name="monochrome">Whether to show the icon as monochrome.</param>
/// <param name="targetSize">The target size for the icon.</param>
/// <returns>An IconSource with its source set, if possible.</returns>
public static IconSource? IconSource(string iconPath, bool monochrome, string? fontFamily = null, int targetSize = 24)
{
if (TryGetIconIndex(iconPath, out var iconPathWithoutIndex, out var index))
{
var bitmapSource = GetImageIconSourceForBinary(iconPathWithoutIndex, index, targetSize);
if (bitmapSource != null)
{
var imageIconSource = new ImageIconSource
{
ImageSource = bitmapSource
};
return imageIconSource;
}
}
return GetIconSource(iconPath, monochrome, fontFamily, targetSize);
}
/// <summary>
/// Creates an IconElement for the given path with default size.
/// </summary>
/// <param name="iconPath">The path to the icon.</param>
/// <returns>An IconElement with its IconSource set, if possible.</returns>
public static IconElement Icon(string iconPath)
{
return Icon(iconPath, 24);
}
/// <summary>
/// Creates an IconElement for the given path.
/// </summary>
/// <param name="iconPath">The path to the icon.</param>
/// <param name="targetSize">The target size for the icon.</param>
/// <returns>An IconElement with its IconSource set, if possible.</returns>
public static IconElement Icon(string iconPath, int targetSize)
{
if (TryGetIconIndex(iconPath, out var iconPathWithoutIndex, out var index))
{
var bitmapSource = GetImageIconSourceForBinary(iconPathWithoutIndex, index, targetSize);
if (bitmapSource != null)
{
var icon = new ImageIcon
{
Source = bitmapSource,
Width = targetSize,
Height = targetSize
};
return icon;
}
}
var source = IconSource(iconPath, false, targetSize);
var iconSourceElement = new IconSourceElement
{
IconSource = source
};
return iconSourceElement;
}
/// <summary>
/// Creates an IconSource for the given path.
/// </summary>
/// <param name="iconPath">The path to the icon.</param>
/// <param name="monochrome">Whether to show the icon as monochrome.</param>
/// <param name="targetSize">The target size for the icon.</param>
/// <returns>An IconSource with its source set, if possible.</returns>
private static IconSource? GetIconSource(string iconPath, bool monochrome, string? fontFamily, int targetSize)
{
IconSource? iconSource = null;
if (!string.IsNullOrEmpty(iconPath))
{
var expandedIconPath = ExpandIconPath(iconPath);
iconSource = GetColoredBitmapIcon(expandedIconPath, monochrome);
// If we fail to set the icon source using the "icon" as a path,
// let's try it as a symbol/emoji.
//
// Anything longer than 2 characters isn't an emoji or symbol, so
// don't do this if it's just an invalid path.
if (iconSource == null && iconPath.Length <= 2)
{
try
{
var icon = new FontIconSource();
var ch = iconPath[0];
// The range of MDL2 Icons isn't explicitly defined, but
// we're using this based off the table on:
// https://docs.microsoft.com/en-us/windows/uwp/design/style/segoe-ui-symbol-font
var isMDL2Icon = ch >= '\uE700' && ch <= '\uF8FF';
if (isMDL2Icon)
{
icon.FontFamily = new FontFamily("Segoe Fluent Icons, Segoe MDL2 Assets");
}
else if (fontFamily != null)
{
icon.FontFamily = new FontFamily(fontFamily);
}
else
{
// Note: you do need to manually set the font here.
icon.FontFamily = new FontFamily("Segoe UI");
}
icon.FontSize = targetSize;
icon.Glyph = iconPath;
iconSource = icon;
}
catch
{
// Ignore exceptions when creating font icons
}
}
}
if (iconSource == null)
{
// Set the default IconSource to a BitmapIconSource with a null source
// (instead of just null) because there's a really weird crash when swapping
// data bound IconSourceElements in a ListViewTemplate (i.e. CommandPalette).
// Swapping between null IconSources and non-null IconSources causes a crash
// to occur, but swapping between IconSources with a null source and non-null IconSources
// work perfectly fine.
var icon = new BitmapIconSource
{
UriSource = null
};
iconSource = icon;
}
return iconSource;
}
/// <summary>
/// Creates a colored bitmap icon source for the given path.
/// </summary>
/// <param name="path">The full, expanded path to the icon.</param>
/// <param name="monochrome">Whether to show the icon as monochrome.</param>
/// <returns>An IconSource with its source set, if possible.</returns>
private static IconSource? GetColoredBitmapIcon(string path, bool monochrome)
{
// FontIcon uses glyphs in the private use area, whereas valid URIs only contain ASCII characters.
// To skip throwing on Uri construction, we can quickly check if the first character is ASCII.
if (!string.IsNullOrEmpty(path) && path[0] < 128)
{
try
{
var iconUri = new Uri(path);
if (Path.GetExtension(path).Equals(".svg", StringComparison.OrdinalIgnoreCase))
{
var iconSource = new ImageIconSource();
var source = new SvgImageSource(iconUri);
iconSource.ImageSource = source;
return iconSource;
}
else
{
var iconSource = new BitmapIconSource
{
// Make sure to set this to false, so we keep the RGB data of the
// image. Otherwise, the icon will be white for all the
// non-transparent pixels in the image.
ShowAsMonochrome = monochrome,
UriSource = iconUri
};
return iconSource;
}
}
catch (UriFormatException)
{
// Ignore exceptions when creating URI-based icons
}
}
return null;
}
/// <summary>
/// Expands environment variables in the icon path.
/// </summary>
/// <param name="iconPath">The path that may contain environment variables.</param>
/// <returns>The expanded path.</returns>
private static string ExpandIconPath(string iconPath)
{
if (string.IsNullOrEmpty(iconPath))
{
return iconPath;
}
// Use Environment.ExpandEnvironmentVariables as the C# equivalent of wil::ExpandEnvironmentStringsW
return Environment.ExpandEnvironmentVariables(iconPath);
}
/// <summary>
/// Attempts to get the icon index from the icon path provided.
/// </summary>
/// <param name="iconPath">The full icon path, including the index if present.</param>
/// <param name="iconPathWithoutIndex">The icon path without the index.</param>
/// <param name="iconIndex">The icon index if present.</param>
/// <returns>True if the iconPath is an exe/dll/lnk file, false otherwise.</returns>
private static bool TryGetIconIndex(string iconPath, out string iconPathWithoutIndex, out int iconIndex)
{
iconPathWithoutIndex = iconPath;
iconIndex = 0;
if (string.IsNullOrEmpty(iconPath))
{
return false;
}
// Does iconPath have a comma in it? If so, split the string on the
// comma and look for the index and extension.
var commaIndex = iconPath.IndexOf(',');
// Split the path on the comma
iconPathWithoutIndex = commaIndex >= 0 ? iconPath.Substring(0, commaIndex) : iconPath;
// It's an exe, dll, or lnk, so we need to extract the icon from the file.
var extension = Path.GetExtension(iconPathWithoutIndex).ToLowerInvariant();
if (extension != ".exe" && extension != ".dll" && extension != ".lnk")
{
return false;
}
if (commaIndex >= 0)
{
// Convert the string iconIndex to a signed int to support negative numbers which represent an Icon's ID.
var indexString = iconPath.Substring(commaIndex + 1);
if (int.TryParse(indexString, out iconIndex))
{
return true;
}
// Failed to parse, return false
return false;
}
// We had a binary path, but no index. Default to 0.
iconIndex = 0;
return true;
}
/// <summary>
/// Gets an image icon source for a binary file (exe, dll, lnk).
/// </summary>
/// <param name="iconPathWithoutIndex">The path to the binary file.</param>
/// <param name="index">The icon index within the file.</param>
/// <param name="targetSize">The target size for the icon.</param>
/// <returns>A SoftwareBitmapSource if successful, null otherwise.</returns>
private static SoftwareBitmapSource? GetImageIconSourceForBinary(string iconPathWithoutIndex, int index, int targetSize)
{
try
{
using var swBitmap = GetBitmapFromIconFile(iconPathWithoutIndex, index, (uint)targetSize);
if (swBitmap == null)
{
return null;
}
var bitmapSource = new SoftwareBitmapSource();
_ = bitmapSource.SetBitmapAsync(swBitmap);
return bitmapSource;
}
catch
{
return null;
}
}
/// <summary>
/// Extracts a bitmap from an icon file using Win32 APIs.
/// This implementation uses SHDefExtractIcon to extract icons from executables, DLLs, and shortcut files.
/// </summary>
/// <param name="iconPath">The path to the icon file.</param>
/// <param name="iconIndex">The index of the icon within the file.</param>
/// <param name="iconSize">The desired icon size.</param>
/// <returns>A SoftwareBitmap if successful, null otherwise.</returns>
private static SoftwareBitmap? GetBitmapFromIconFile(string iconPath, int iconIndex, uint iconSize)
{
try
{
DestroyIconSafeHandle? hIconLarge;
DestroyIconSafeHandle? hIconSmall;
// Extract the icon using SHDefExtractIcon
var result = PInvoke.SHDefExtractIcon(
iconPath,
iconIndex,
0,
out hIconLarge,
out hIconSmall,
iconSize);
using (hIconLarge)
using (hIconSmall)
{
if (result != 0 || hIconLarge.IsInvalid)
{
return null;
}
// For simplicity, convert HICON to bitmap using a more straightforward approach
// This could be enhanced to use WIC directly for better performance
return ConvertHIconToSoftwareBitmap(hIconLarge);
}
}
catch
{
return null;
}
}
/// <summary>
/// Converts an HICON to a SoftwareBitmap using Win32 APIs.
/// </summary>
/// <param name="hIcon">The icon handle to convert.</param>
/// <returns>A SoftwareBitmap if successful, null otherwise.</returns>
private static unsafe SoftwareBitmap? ConvertHIconToSoftwareBitmap(DestroyIconSafeHandle hIcon)
{
// Get icon information
if (!PInvoke.GetIconInfo(hIcon, out var iconInfo))
{
return null;
}
// Create device contexts
var hdcScreen = PInvoke.GetDC(HWND.Null);
var hdcMem = PInvoke.CreateCompatibleDC(hdcScreen);
// Get the icon size (assuming 32x32 for now, could be enhanced to get actual size)
const int iconSize = 32;
// Create a bitmap to draw the icon onto
using var hBitmap = PInvoke.CreateCompatibleBitmap_SafeHandle(hdcScreen, iconSize, iconSize);
using var hOldBitmap = PInvoke.SelectObject(hdcMem, hBitmap);
// Draw the icon onto the bitmap
PInvoke.DrawIconEx(hdcMem, 0, 0, hIcon, iconSize, iconSize, 0, null, (DI_FLAGS)0x0003); // DI_NORMAL
// Get bitmap info
var bitmapInfo = new BITMAPINFO();
bitmapInfo.bmiHeader.biSize = (uint)sizeof(BITMAPINFOHEADER);
bitmapInfo.bmiHeader.biWidth = iconSize;
bitmapInfo.bmiHeader.biHeight = -iconSize; // Top-down DIB
bitmapInfo.bmiHeader.biPlanes = 1;
bitmapInfo.bmiHeader.biBitCount = 32;
bitmapInfo.bmiHeader.biCompression = 0; // BI_RGB
// Allocate buffer for pixel data
var pixelDataSize = iconSize * iconSize * 4; // 4 bytes per pixel (BGRA)
var pixelData = new byte[pixelDataSize];
// Get the pixel data - need to pin the byte array
fixed (byte* pixelPtr = pixelData)
{
var result = PInvoke.GetDIBits(
hdcMem,
hBitmap,
0,
iconSize,
pixelPtr,
&bitmapInfo,
0); // DIB_RGB_COLORS
if (result == 0)
{
return null;
}
}
// Create SoftwareBitmap from pixel data
var softwareBitmap = SoftwareBitmap.CreateCopyFromBuffer(
pixelData.AsBuffer(),
BitmapPixelFormat.Bgra8,
iconSize,
iconSize,
BitmapAlphaMode.Premultiplied);
return softwareBitmap;
}
}

View File

@@ -5,8 +5,9 @@
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.CmdPal.Core.ViewModels.Messages;
using Microsoft.CmdPal.UI.Messages;
using Microsoft.CmdPal.UI.ViewModels;
using Microsoft.CmdPal.UI.ViewModels.Messages;
using Microsoft.UI.Xaml;
using Windows.Win32;
using Windows.Win32.Foundation;

View File

@@ -29,7 +29,7 @@ public static class WindowExtensions
}
catch (NotImplementedException)
{
// SetShownInSwitchers failed. This can happen if the Explorer is not running.
// Setting IsShownInSwitchers failed. This can happen if the Explorer is not running.
}
}
}

View File

@@ -10,11 +10,12 @@ using ManagedCommon;
using Microsoft.CmdPal.Common.Helpers;
using Microsoft.CmdPal.Common.Messages;
using Microsoft.CmdPal.Common.Services;
using Microsoft.CmdPal.Core.ViewModels;
using Microsoft.CmdPal.Core.ViewModels.Messages;
using Microsoft.CmdPal.UI.Events;
using Microsoft.CmdPal.UI.Helpers;
using Microsoft.CmdPal.UI.Messages;
using Microsoft.CmdPal.UI.ViewModels;
using Microsoft.CmdPal.UI.ViewModels.Messages;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.PowerToys.Telemetry;
using Microsoft.UI.Composition;
@@ -176,7 +177,11 @@ public sealed partial class MainWindow : WindowEx,
private void UpdateAcrylic()
{
_acrylicController?.RemoveAllSystemBackdropTargets();
if (_acrylicController != null)
{
_acrylicController.RemoveAllSystemBackdropTargets();
_acrylicController.Dispose();
}
_acrylicController = GetAcrylicConfig(Content);

View File

@@ -2,7 +2,7 @@
// 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.Core.ViewModels.Messages;
namespace Microsoft.CmdPal.UI.Messages;
public record HotkeySummonMessage(string CommandId, IntPtr Hwnd)
{

View File

@@ -2,11 +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 Microsoft.CmdPal.Core.ViewModels.Messages;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls.Primitives;
using Windows.Foundation;
namespace Microsoft.CmdPal.Core.ViewModels.Messages;
namespace Microsoft.CmdPal.UI.Messages;
/// <summary>
/// Used to announce the context menu should open

View File

@@ -2,7 +2,7 @@
// 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.Core.ViewModels.Messages;
namespace Microsoft.CmdPal.UI.Messages;
public record SettingsWindowClosedMessage
{

View File

@@ -109,10 +109,6 @@
<ProjectCapability Include="Msix" />
</ItemGroup>
<ItemGroup>
<RdXmlFile Include="rd.xml" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\common\ManagedTelemetry\Telemetry\ManagedTelemetry.csproj" />
<ProjectReference Include="..\ext\Microsoft.CmdPal.Ext.ClipboardHistory\Microsoft.CmdPal.Ext.ClipboardHistory.csproj" />

View File

@@ -225,11 +225,11 @@
HorizontalAlignment="Center"
VerticalAlignment="Center"
ui:VisualExtensions.NormalizedCenterPoint="0.5,0.5"
Click="BackButton_Clicked"
Content="{ui:FontIcon Glyph=&#xE76B;,
FontSize=14}"
FontSize="16"
Style="{StaticResource SubtleButtonStyle}"
Tapped="BackButton_Tapped"
Visibility="{x:Bind ViewModel.CurrentPage.IsNested, Mode=OneWay}">
<animations:Implicit.ShowAnimations>
<animations:OpacityAnimation

View File

@@ -9,6 +9,7 @@ using ManagedCommon;
using Microsoft.CmdPal.Core.ViewModels;
using Microsoft.CmdPal.Core.ViewModels.Messages;
using Microsoft.CmdPal.UI.Events;
using Microsoft.CmdPal.UI.Messages;
using Microsoft.CmdPal.UI.Settings;
using Microsoft.CmdPal.UI.ViewModels;
using Microsoft.CommandPalette.Extensions;
@@ -17,7 +18,6 @@ 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;
@@ -242,6 +242,7 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
}
_settingsWindow.Activate();
_settingsWindow.BringToFront();
}
public void Receive(ShowDetailsMessage message)
@@ -413,7 +414,7 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
}
}
private void BackButton_Tapped(object sender, Microsoft.UI.Xaml.Input.TappedRoutedEventArgs e) => WeakReferenceMessenger.Default.Send<NavigateBackMessage>(new());
private void BackButton_Clicked(object sender, Microsoft.UI.Xaml.RoutedEventArgs e) => WeakReferenceMessenger.Default.Send<NavigateBackMessage>(new());
private void RootFrame_Navigated(object sender, Microsoft.UI.Xaml.Navigation.NavigationEventArgs e)
{

View File

@@ -4,9 +4,10 @@
using System.Collections.ObjectModel;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.CmdPal.Core.ViewModels.Messages;
using Microsoft.CmdPal.UI.Helpers;
using Microsoft.CmdPal.UI.Messages;
using Microsoft.CmdPal.UI.ViewModels;
using Microsoft.CmdPal.UI.ViewModels.Messages;
using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;

View File

@@ -428,4 +428,10 @@ Right-click to remove the key combination, thereby deactivating the shortcut.</v
<data name="Settings_ExtensionPage_Alias_ToggleSwitch.OffContent" xml:space="preserve">
<value>Indirect</value>
</data>
<data name="StatusMessagesButton.[using:Microsoft.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
<value>Show status messages</value>
</data>
<data name="StatusMessagesButton.[using:Microsoft.UI.Xaml.Controls]ToolTipService.ToolTip" xml:space="preserve">
<value>Show status messages</value>
</data>
</root>

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