Compare commits

..

9 Commits

Author SHA1 Message Date
Shawn Yuan
ea43974287 [Settings] [Advanced Paste] Upgrade advanced paste settings safely to fix settings ui crash (#44862)
<!-- 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 makes a minor fix in the `AdvancedPasteViewModel`
constructor to ensure the correct settings repository is used for null
checking. The change improves code correctness by verifying
`advancedPasteSettingsRepository` instead of the generic
`settingsRepository`.

- Fixed null check to use `advancedPasteSettingsRepository` instead of
`settingsRepository` in the `AdvancedPasteViewModel` constructor for
more accurate validation.
<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

- [x] Closes: #44835
<!-- - [ ] Closes: #yyy (add separate lines for additional resolved
issues) -->
- [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
2026-01-26 15:28:59 +08:00
Gordon Lam
0b3dc089ac [Peek] Fix Space key triggering during file rename (#44845) (#44995)
Don't show error window when CurrentItem is null - just return silently.
This restores the original behavior where CaretVisible() detection in
GetSelectedItems() would suppress Peek by returning null, and no window
would be shown.

PR #44703 added an error window for virtual folders (Home/Recent), but
this also triggered when user was typing (rename, search, address bar),
stealing focus and cancelling the operation.

Fixes #44845

<!-- 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
2026-01-26 15:20:07 +08:00
Shawn Yuan
4ba6fd2723 Add telemetry for tray icon (#44985)
<!-- 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 adds telemetry tracking for user interactions with the
application's tray icon. Specifically, it introduces new methods for
logging `left-click`, `right-click`, and `double-click` events, and
integrates these telemetry calls into the tray icon event handling
logic.

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

- [ ] Closes: #xxx
<!-- - [ ] Closes: #yyy (add separate lines for additional resolved
issues) -->
- [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
2026-01-26 09:44:10 +08:00
Shawn Yuan
086c63b6af [Settings] Fix right click menu display issue (#44982)
<!-- 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 updates the tray icon context menu logic to better
reflect the state of the "Quick Access" feature. The menu now
dynamically updates its items and labels based on whether Quick Access
is enabled or disabled, improving clarity for users.

**Menu behavior improvements:**

* The tray icon menu now reloads itself when the Quick Access setting
changes, ensuring the menu always matches the current state.
* The "Settings" menu item label changes to "Settings\tLeft-click" when
Quick Access is disabled, providing clearer instructions to users.
[[1]](diffhunk://#diff-e5efbda4c356e159a6ca82a425db84438ab4014d1d90377b98a2eb6d9632d32dR176-R179)
[[2]](diffhunk://#diff-7139ecb2cf76e472c574a155268c19e919e2cce05d9d345c50c1f1bffc939e1aR198-R248)
* The Quick Access menu item is removed from the context menu when the
feature is disabled, preventing confusion.

**Internal state tracking:**

* Added a new variable `last_quick_access_state` to track the previous
Quick Access state and trigger menu reloads only when necessary.
<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

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

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

<img width="1537" height="312" alt="image"
src="https://github.com/user-attachments/assets/5d51f24e-ccb4-4973-afaa-8b64cc35db87"
/>

- When Quick Access is enabled
<img width="1601" height="201" alt="image"
src="https://github.com/user-attachments/assets/56366d10-bcec-4892-b2d2-f8213ad726aa"
/>

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2026-01-23 14:47:35 +08:00
moooyo
d192672c74 fix: Improve Unicode normalization and add regex metachar tests (#44944)
Enhanced SanitizeAndNormalize to handle Unicode normalization more
robustly, ensuring correct buffer sizing and error handling. Added unit
tests for regex metacharacters `$` and `^` to verify correct replacement
behavior at string boundaries. Improves Unicode support and test
coverage for regex edge cases.

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

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

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

---------

Co-authored-by: Yu Leng <yuleng@microsoft.com>
2026-01-23 14:43:21 +08:00
Kai Tao
60b8419366 Runner TrayIcon: Monochrome icon should adapt to windows theme instead of the app theme (#44931)
<!-- 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
As title
<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

- [X] Closes: #44891
<!-- - [ ] Closes: #yyy (add separate lines for additional resolved
issues) -->
- [ ] **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
============ System light + App Light
<img width="903" height="239" alt="image"
src="https://github.com/user-attachments/assets/581606fb-99b5-4df9-a520-545a0c04676c"
/>
============ System Light + App Dark
<img width="991" height="239" alt="image"
src="https://github.com/user-attachments/assets/822009e9-57cf-452b-b3aa-f1cbc25883f8"
/>
============ System Dark + App Light
<img width="932" height="236" alt="image"
src="https://github.com/user-attachments/assets/98a56d48-31f0-4f75-95a4-8c7dc83c3866"
/>
============ System Dark + App Dark
<img width="903" height="236" alt="image"
src="https://github.com/user-attachments/assets/2500a0d5-6b27-403e-89b4-69b7d3b91e79"
/>
============
2026-01-23 10:47:19 +08:00
Kai Tao
d46a996fcd Cmdpal: use latest msix to install (#44886)
<!-- 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
We should install latest cmdpal msix
<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

- [ ] Closes: #44860
<!-- - [ ] Closes: #yyy (add separate lines for additional resolved
issues) -->
- [ ] **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
2026-01-23 09:58:53 +08:00
Jiří Polášek
395850389f CmdPal: Improve loading of application icons - part 1 (#44938)
## Summary of the Pull Request

This PR improves loading of application icons:

- Fixes loading of icons from internet shortcuts

## Pictures? Pictures!

<img width="683" height="399" alt="image"
src="https://github.com/user-attachments/assets/5e566648-7b1a-4254-8afd-557a321b19d6"
/>


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

- [x] Closes: #44819
<!-- - [ ] Closes: #yyy (add separate lines for additional resolved
issues) -->
- [ ] **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
2026-01-22 19:25:24 +01:00
Jessica Dene Earley-Cha
fbabdffcfa Update DATA_AND_PRIVACY to reflect current status (#44844)
<!-- 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 the telemetry events documentation in `DATA_AND_PRIVACY.md` to
ensure completeness and improve organization.
- Added missing & new events
- Removed obsolete events (deprecated or unused event)
- Alphabetized all modules for easier navigation and maintenance
- Made all tables uniform with `table-layout:fixed`



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

- [ ] Closes: #xxx
<!-- - [ ] Closes: #yyy (add separate lines for additional resolved
issues) -->
- [ ] **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
2026-01-21 11:07:12 -08:00
59 changed files with 777 additions and 2100 deletions

View File

@@ -103,7 +103,6 @@ ASYNCWINDOWPLACEMENT
ASYNCWINDOWPOS
atl
ATRIOX
ATX
aumid
authenticode
AUTOBUDDY
@@ -298,7 +297,6 @@ cpcontrols
cph
cplusplus
CPower
cppcoreguidelines
cpptools
cppvsdbg
cppwinrt
@@ -327,7 +325,7 @@ CURRENTDIR
CURSORINFO
cursorpos
CURSORSHOWING
CURSORWRAP
cursorwrap
customaction
CUSTOMACTIONTEST
CUSTOMFORMATPLACEHOLDER
@@ -888,6 +886,7 @@ Ldr
LEFTALIGN
LEFTSCROLLBAR
LEFTTEXT
leftclick
LError
LEVELID
LExit
@@ -1013,6 +1012,7 @@ MENUITEMINFO
MENUITEMINFOW
MERGECOPY
MERGEPAINT
Metacharacter
metadatamatters
Metadatas
metafile
@@ -1760,7 +1760,6 @@ SUBMODULEUPDATE
subresource
Superbar
sut
swe
svchost
SVGIn
SVGIO

View File

@@ -30,7 +30,11 @@ _If you want to find diagnostic data events in the source code, these two links
- [C++ events](https://github.com/search?q=repo%3Amicrosoft%2FPowerToys+ProjectTelemetryPrivacyDataTag&type=code)
### General
<table style="width:100%">
<table style="width:100%; table-layout:fixed">
<colgroup>
<col style="width:40%">
<col style="width:60%">
</colgroup>
<tr>
<th>Event Name</th>
<th>Description</th>
@@ -43,6 +47,18 @@ _If you want to find diagnostic data events in the source code, these two links
<td>Microsoft.PowerToys.GeneralSettingsChanged</td>
<td>Logs changes made to general settings within PowerToys.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.Install_Fail</td>
<td>Triggered when the PowerToys installation process encounters an error and fails to complete.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.Repair_Cancel</td>
<td>Triggered when a PowerToys repair operation is cancelled by the user before completion.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.Repair_Fail</td>
<td>Triggered when the PowerToys repair operation fails to complete successfully due to an error.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.Runner_Launch</td>
<td>Indicates when the PowerToys Runner is launched.</td>
@@ -59,6 +75,18 @@ _If you want to find diagnostic data events in the source code, these two links
<td>Microsoft.PowerToys.ScoobeStartedEvent</td>
<td>Triggered when SCOOBE (Secondary Out-of-box experience) starts.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.ShortcutConflictControlClickedEvent</td>
<td>Triggered when a user clicks on the Shortcut Conflict Control button in the PowerToys Settings UI Dashboard.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.ShortcutConflictDetectedEvent</td>
<td>Triggered when keyboard shortcut conflicts are detected in the PowerToys Settings UI Dashboard.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.ShortcutConflictResolvedEvent</td>
<td>Triggered when a keyboard shortcut conflict is resolved in the PowerToys Settings UI.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.TrayFlyoutActivatedEvent</td>
<td>Indicates when the tray flyout menu is activated.</td>
@@ -67,6 +95,14 @@ _If you want to find diagnostic data events in the source code, these two links
<td>Microsoft.PowerToys.TrayFlyoutModuleRunEvent</td>
<td>Logs when a utility from the tray flyout menu is run.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.UnInstall_Cancel</td>
<td>Triggered when the PowerToys uninstallation process is cancelled by the user before completion.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.UnInstall_Fail</td>
<td>Triggered when the PowerToys uninstallation process fails to complete successfully due to an error. </td>
</tr>
<tr>
<td>Microsoft.PowerToys.Uninstall_Success</td>
<td>Logs when PowerToys is successfully uninstalled (who would do such a thing!).</td>
@@ -82,11 +118,19 @@ _If you want to find diagnostic data events in the source code, these two links
</table>
### OOBE (Out-of-box experience)
<table style="width:100%">
<table style="width:100%; table-layout:fixed">
<colgroup>
<col style="width:40%">
<col style="width:60%">
</colgroup>
<tr>
<th>Event Name</th>
<th>Description</th>
</tr>
<tr>
<td>Microsoft.PowerToys.OobeModuleRunEvent</td>
<td>Triggered when a user clicks to run or launch a PowerToys module directly from the OOBE (out-of-box experience) interface.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.OobeSectionEvent</td>
<td>Occurs when OOBE is shown to the user.</td>
@@ -99,10 +143,18 @@ _If you want to find diagnostic data events in the source code, these two links
<td>Microsoft.PowerToys.OobeStartedEvent</td>
<td>Indicates when the out-of-box experience has been initiated.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.OobeVariantAssignmentEvent</td>
<td>This event logs A/B testing assignments for experimental features, helping track which users are in control or alternate groups for feature experiments. </td>
</tr>
</table>
### Advanced Paste
<table style="width:100%">
<table style="width:100%; table-layout:fixed">
<colgroup>
<col style="width:40%">
<col style="width:60%">
</colgroup>
<tr>
<th>Event Name</th>
<th>Description</th>
@@ -170,7 +222,11 @@ _If you want to find diagnostic data events in the source code, these two links
</table>
### Always on Top
<table style="width:100%">
<table style="width:100%; table-layout:fixed">
<colgroup>
<col style="width:40%">
<col style="width:60%">
</colgroup>
<tr>
<th>Event Name</th>
<th>Description</th>
@@ -190,7 +246,11 @@ _If you want to find diagnostic data events in the source code, these two links
</table>
### Awake
<table style="width:100%">
<table style="width:100%; table-layout:fixed">
<colgroup>
<col style="width:40%">
<col style="width:60%">
</colgroup>
<tr>
<th>Event Name</th>
<th>Description</th>
@@ -218,7 +278,11 @@ _If you want to find diagnostic data events in the source code, these two links
</table>
### Color Picker
<table style="width:100%">
<table style="width:100%; table-layout:fixed">
<colgroup>
<col style="width:40%">
<col style="width:60%">
</colgroup>
<tr>
<th>Event Name</th>
<th>Description</th>
@@ -235,18 +299,14 @@ _If you want to find diagnostic data events in the source code, these two links
<td>Microsoft.PowerToys.ColorPicker_Settings</td>
<td>Triggered when the settings for the Color Picker are accessed or modified.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.ColorPickerCancelledEvent</td>
<td>Occurs when a color picking action is cancelled by the user.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.ColorPickerShowEvent</td>
<td>Triggered when the Color Picker UI is displayed on the screen.</td>
</tr>
</table>
### Command Not Found
<table style="width:100%">
<table style="width:100%; table-layout:fixed">
<colgroup>
<col style="width:40%">
<col style="width:60%">
</colgroup>
<tr>
<th>Event Name</th>
<th>Description</th>
@@ -259,10 +319,6 @@ _If you want to find diagnostic data events in the source code, these two links
<td>Microsoft.PowerToys.CmdNotFoundInstallEvent</td>
<td>Triggered when a Command Not Found is installed.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.CmdNotFoundInstanceCreatedEvent</td>
<td>Occurs when an instance of a Command Not Found is created.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.CmdNotFoundUninstallEvent</td>
<td>Triggered when Command Not Found is uninstalled after being previously installed.</td>
@@ -271,7 +327,11 @@ _If you want to find diagnostic data events in the source code, these two links
### Command Palette
<table style="width:100%">
<table style="width:100%; table-layout:fixed">
<colgroup>
<col style="width:40%">
<col style="width:60%">
</colgroup>
<tr>
<th>Event Name</th>
<th>Description</th>
@@ -335,7 +395,11 @@ _If you want to find diagnostic data events in the source code, these two links
</table>
### Crop And Lock
<table style="width:100%">
<table style="width:100%; table-layout:fixed">
<colgroup>
<col style="width:40%">
<col style="width:60%">
</colgroup>
<tr>
<th>Event Name</th>
<th>Description</th>
@@ -344,10 +408,26 @@ _If you want to find diagnostic data events in the source code, these two links
<td>Microsoft.PowerToys.CropAndLock_ActivateReparent</td>
<td>Triggered when the cropping interface is activated for reparenting the cropped content.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.CropAndLock_ActivateScreenshot</td>
<td>Triggered when the screenshot mode is activated in Crop and Lock.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.CropAndLock_ActivateThumbnail</td>
<td>Occurs when the thumbnail view for cropped content is activated.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.CropAndLock_CreateReparentWindow</td>
<td>Triggered when a reparent window is created in Crop and Lock mode.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.CropAndLock_CreateScreenshotWindow</td>
<td>Triggered when a screenshot window is created in Crop and Lock mode.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.CropAndLock_CreateThumbnailWindow</td>
<td>Triggered when a thumbnail window is created in Crop and Lock mode.<-/td>
</tr>
<tr>
<td>Microsoft.PowerToys.CropAndLock_EnableCropAndLock</td>
<td>Triggered when Crop and Lock is enabled.</td>
@@ -358,8 +438,28 @@ _If you want to find diagnostic data events in the source code, these two links
</tr>
</table>
### Cursor Wrap
<table style="width:100%; table-layout:fixed">
<colgroup>
<col style="width:40%">
<col style="width:60%">
</colgroup>
<tr>
<th>Event Name</th>
<th>Description</th>
</tr>
<tr>
<td>Microsoft.PowerToys.CursorWrap_EnableCursorWrap</td>
<td>Triggered when Cursor Wrap is enabled or disabled.</td>
</tr>
</table>
### Environment Variables
<table style="width:100%">
<table style="width:100%; table-layout:fixed">
<colgroup>
<col style="width:40%">
<col style="width:60%">
</colgroup>
<tr>
<th>Event Name</th>
<th>Description</th>
@@ -387,7 +487,11 @@ _If you want to find diagnostic data events in the source code, these two links
</table>
### FancyZones
<table style="width:100%">
<table style="width:100%; table-layout:fixed">
<colgroup>
<col style="width:40%">
<col style="width:60%">
</colgroup>
<tr>
<th>Event Name</th>
<th>Description</th>
@@ -404,6 +508,10 @@ _If you want to find diagnostic data events in the source code, these two links
<td>Microsoft.PowerToys.FancyZones_EnableFancyZones</td>
<td>Occurs when FancyZones is enabled.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.FancyZones_Error</td>
<td>Triggered when an error occurs within the FancyZones module. This event logs critical errors to help diagnose and troubleshoot issues with FancyZones functionality, such as failures to set up Windows hooks or other system-level operations required for window management.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.FancyZones_KeyboardSnapWindowToZone</td>
<td>Triggered when a window is snapped to a zone using the keyboard.</td>
@@ -416,10 +524,6 @@ _If you want to find diagnostic data events in the source code, these two links
<td>Microsoft.PowerToys.FancyZones_MoveOrResizeStarted</td>
<td>Triggered when a window move or resize action is initiated.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.FancyZones_MoveSizeEnd</td>
<td>Occurs when the moving or resizing of a window has ended.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.FancyZones_OnKeyDown</td>
<td>Triggered when a key is pressed down while interacting with zones.</td>
@@ -432,10 +536,6 @@ _If you want to find diagnostic data events in the source code, these two links
<td>Microsoft.PowerToys.FancyZones_Settings</td>
<td>Triggered when FancyZones settings are accessed or modified.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.FancyZones_SettingsChanged</td>
<td>Occurs when there is a change in the FancyZones settings.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.FancyZones_SnapNewWindowIntoZone</td>
<td>Triggered when a new window is snapped into a zone.</td>
@@ -456,10 +556,50 @@ _If you want to find diagnostic data events in the source code, these two links
<td>Microsoft.PowerToys.FancyZones_CLICommand</td>
<td>Triggered when a FancyZones CLI command is executed, logging the command name and success status.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.FancyZonesEditorStartEvent</td>
<td>Triggered when the FancyZones Editor application starts. This logs the initialization of the editor UI, which is used to create and configure custom zone layouts.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.FancyZonesEditorStartFinishEvent</td>
<td>Triggered when the FancyZones Editor has completed loading and is ready for user interaction.</td>
</tr>
</table>
### File Locksmith
<table style="width:100%; table-layout:fixed">
<colgroup>
<col style="width:40%">
<col style="width:60%">
</colgroup>
<tr>
<th>Event Name</th>
<th>Description</th>
</tr>
<tr>
<td>Microsoft.PowerToys.FileLocksmith_EnableFileLocksmith</td>
<td>Triggered when File Locksmith is enabled.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.FileLocksmith_Invoked</td>
<td>Occurs when File Locksmith is invoked.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.FileLocksmith_InvokedRet</td>
<td>Triggered when File Locksmith invocation returns a result.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.FileLocksmith_QueryContextMenuError</td>
<td>Occurs when there is an error querying the context menu for File Locksmith.</td>
</tr>
</table>
### FileExplorerAddOns
<table style="width:100%">
<table style="width:100%; table-layout:fixed">
<colgroup>
<col style="width:40%">
<col style="width:60%">
</colgroup>
<tr>
<th>Event Name</th>
<th>Description</th>
@@ -496,6 +636,10 @@ _If you want to find diagnostic data events in the source code, these two links
<td>Microsoft.PowerToys.MarkdownFilePreviewed</td>
<td>Triggered when a Markdown file is previewed in File Explorer.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.MarkdownFilePreviewError</td>
<td>Triggered when there is an error previewing a Markdown file in File Explorer.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.PdfFileHandlerLoaded</td>
<td>Occurs when a PDF file handler is loaded.</td>
@@ -504,6 +648,10 @@ _If you want to find diagnostic data events in the source code, these two links
<td>Microsoft.PowerToys.PdfFilePreviewed</td>
<td>Triggered when a PDF file is previewed in File Explorer.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.PdfFilePreviewError</td>
<td>Triggered when there is an error previewing a PDF file in File Explorer.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.PowerPreview_Enabled</td>
<td>Occurs when preview is enabled.</td>
@@ -520,6 +668,10 @@ _If you want to find diagnostic data events in the source code, these two links
<td>Microsoft.PowerToys.PowerPreview_TweakUISettings_InitSet__ErrorLoadingFile</td>
<td>Triggered when there is an error loading a file during Tweak UI settings initialization.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.PowerPreview_TweakUISettings_SetConfig__InvalidJSONGiven</td>
<td>Triggered when invalid JSON is provided to the Power Preview settings configuration</td>
</tr>
<tr>
<td>Microsoft.PowerToys.PowerPreview_TweakUISettings_SuccessfullyUpdatedSettings</td>
<td>Occurs when the Tweak UI settings for Power Preview are successfully updated.</td>
@@ -528,6 +680,10 @@ _If you want to find diagnostic data events in the source code, these two links
<td>Microsoft.PowerToys.QoiFilePreviewed</td>
<td>Triggered when a QOI file is previewed in File Explorer.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.QoiFilePreviewError</td>
<td>Triggered when there is an error previewing a QOI (Quite OK Image) file in File Explorer.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.SvgFileHandlerLoaded</td>
<td>Occurs when an SVG file handler is loaded.</td>
@@ -542,32 +698,12 @@ _If you want to find diagnostic data events in the source code, these two links
</tr>
</table>
### File Locksmith
<table style="width:100%">
<tr>
<th>Event Name</th>
<th>Description</th>
</tr>
<tr>
<td>Microsoft.PowerToys.FileLocksmith_EnableFileLocksmith</td>
<td>Triggered when File Locksmith is enabled.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.FileLocksmith_Invoked</td>
<td>Occurs when File Locksmith is invoked.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.FileLocksmith_InvokedRet</td>
<td>Triggered when File Locksmith invocation returns a result.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.FileLocksmith_QueryContextMenuError</td>
<td>Occurs when there is an error querying the context menu for File Locksmith.</td>
</tr>
</table>
### Find My Mouse
<table style="width:100%">
<table style="width:100%; table-layout:fixed">
<colgroup>
<col style="width:40%">
<col style="width:60%">
</colgroup>
<tr>
<th>Event Name</th>
<th>Description</th>
@@ -583,7 +719,11 @@ _If you want to find diagnostic data events in the source code, these two links
</table>
### Hosts File Editor
<table style="width:100%">
<table style="width:100%; table-layout:fixed">
<colgroup>
<col style="width:40%">
<col style="width:60%">
</colgroup>
<tr>
<th>Event Name</th>
<th>Description</th>
@@ -600,10 +740,22 @@ _If you want to find diagnostic data events in the source code, these two links
<td>Microsoft.PowerToys.HostsFileEditorOpenedEvent</td>
<td>Fires when Hosts File Editor is opened.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.HostEditorStartEvent</td>
<td>Triggered when the Hosts File Editor application starts. This logs the initialization of the Hosts File Editor UI with a timestamp.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.HostEditorStartFinishEvent</td>
<td>Triggered when the Hosts File Editor has completed loading and is ready for user interaction.</td>
</tr>
</table>
### Image Resizer
<table style="width:100%">
<table style="width:100%; table-layout:fixed">
<colgroup>
<col style="width:40%">
<col style="width:60%">
</colgroup>
<tr>
<th>Event Name</th>
<th>Description</th>
@@ -620,10 +772,18 @@ _If you want to find diagnostic data events in the source code, these two links
<td>Microsoft.PowerToys.ImageResizer_InvokedRet</td>
<td>Fires when the Image Resizer operation is completed and returns a result.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.ImageResizer_QueryContextMenuError</td>
<td>Triggered when there is an error querying the context menu for Image Resizer.</td>
</tr>
</table>
### Keyboard Manager
<table style="width:100%">
<table style="width:100%; table-layout:fixed">
<colgroup>
<col style="width:40%">
<col style="width:60%">
</colgroup>
<tr>
<th>Event Name</th>
<th>Description</th>
@@ -636,10 +796,22 @@ _If you want to find diagnostic data events in the source code, these two links
<td>Microsoft.PowerToys.KeyboardManager_AppSpecificShortcutRemapCount</td>
<td>Logs the number of application-specific shortcut remaps configured by the user.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.KeyboardManager_AppSpecificShortcutToKeyRemapInvoked</td>
<td>Logs each instance when an application-specific shortcut-to-key remap is used.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.KeyboardManager_AppSpecificShortcutToShortcutRemapInvoked</td>
<td>Logs each instance when an application-specific shortcut-to-shortcut remap is used.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.KeyboardManager_Error</td>
<td>Triggered when an error occurs in Keyboard Manager. This logs the error code, error message, and the method name where the error occurred.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.KeyboardManager_ErrorSendingKeyAndShortcutRemapLoadedConfiguration</td>
<td>Triggered when there is an error sending remapping configuration telemetry. This occurs when Keyboard Manager fails to report the loaded key and shortcut remap configurations</td>
</tr>
<tr>
<td>Microsoft.PowerToys.KeyboardManager_DailyAppSpecificShortcutToKeyRemapInvoked</td>
<td>Logs the daily count of application-specific shortcut-to-key remaps executed by the user.</td>
@@ -703,7 +875,11 @@ _If you want to find diagnostic data events in the source code, these two links
</table>
### Light Switch
<table style="width:100%">
<table style="width:100%; table-layout:fixed">
<colgroup>
<col style="width:40%">
<col style="width:60%">
</colgroup>
<tr>
<th>Event Name</th>
<th>Description</th>
@@ -727,7 +903,11 @@ _If you want to find diagnostic data events in the source code, these two links
</table>
### Mouse Highlighter
<table style="width:100%">
<table style="width:100%; table-layout:fixed">
<colgroup>
<col style="width:40%">
<col style="width:60%">
</colgroup>
<tr>
<th>Event Name</th>
<th>Description</th>
@@ -740,10 +920,18 @@ _If you want to find diagnostic data events in the source code, these two links
<td>Microsoft.PowerToys.MouseHighlighter_StartHighlightingSession</td>
<td>Occurs when a new highlighting session is started.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.MouseHighlighter_StartSpotlightSession</td>
<td>Triggered when a spotlight session is started in Mouse Highlighter. This occurs when the user activates the spotlight mode.</td>
</tr>
</table>
### Mouse Jump
<table style="width:100%">
<table style="width:100%; table-layout:fixed">
<colgroup>
<col style="width:40%">
<col style="width:60%">
</colgroup>
<tr>
<th>Event Name</th>
<th>Description</th>
@@ -767,7 +955,11 @@ _If you want to find diagnostic data events in the source code, these two links
</table>
### Mouse Pointer Crosshairs
<table style="width:100%">
<table style="width:100%; table-layout:fixed">
<colgroup>
<col style="width:40%">
<col style="width:60%">
</colgroup>
<tr>
<th>Event Name</th>
<th>Description</th>
@@ -783,7 +975,11 @@ _If you want to find diagnostic data events in the source code, these two links
</table>
### Mouse Without Borders
<table style="width:100%">
<table style="width:100%; table-layout:fixed">
<colgroup>
<col style="width:40%">
<col style="width:60%">
</colgroup>
<tr>
<th>Event Name</th>
<th>Description</th>
@@ -835,7 +1031,11 @@ _If you want to find diagnostic data events in the source code, these two links
</table>
### New+
<table style="width:100%">
<table style="width:100%; table-layout:fixed">
<colgroup>
<col style="width:40%">
<col style="width:60%">
</colgroup>
<tr>
<th>Event Name</th>
<th>Description</th>
@@ -867,7 +1067,11 @@ _If you want to find diagnostic data events in the source code, these two links
</table>
### Peek
<table style="width:100%">
<table style="width:100%; table-layout:fixed">
<colgroup>
<col style="width:40%">
<col style="width:60%">
</colgroup>
<tr>
<th>Event Name</th>
<th>Description</th>
@@ -900,10 +1104,18 @@ _If you want to find diagnostic data events in the source code, these two links
<td>Microsoft.PowerToys.Peek_Settings</td>
<td>Triggered when the settings for Peek are modified.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.Peek_SpaceModeEnabled</td>
<td>Triggered when the Space key activation mode is enabled or disabled in Peek</td>
</tr>
</table>
### PowerRename
<table style="width:100%">
<table style="width:100%; table-layout:fixed">
<colgroup>
<col style="width:40%">
<col style="width:60%">
</colgroup>
<tr>
<th>Event Name</th>
<th>Description</th>
@@ -935,7 +1147,11 @@ _If you want to find diagnostic data events in the source code, these two links
</table>
### PowerToys Run
<table style="width:100%">
<table style="width:100%; table-layout:fixed">
<colgroup>
<col style="width:40%">
<col style="width:60%">
</colgroup>
<tr>
<th>Event Name</th>
<th>Description</th>
@@ -976,14 +1192,14 @@ _If you want to find diagnostic data events in the source code, these two links
<td>Microsoft.PowerToys.RunPluginsSettingsEvent</td>
<td>Triggered when the settings for PowerToys Run plugins are accessed or modified.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.WindowWalker_EnableWindowWalker</td>
<td>Triggered when the Window Walker plugin is enabled.</td>
</tr>
</table>
### Quick Accent
<table style="width:100%">
<table style="width:100%; table-layout:fixed">
<colgroup>
<col style="width:40%">
<col style="width:60%">
</colgroup>
<tr>
<th>Event Name</th>
<th>Description</th>
@@ -999,7 +1215,11 @@ _If you want to find diagnostic data events in the source code, these two links
</table>
### Registry Preview
<table style="width:100%">
<table style="width:100%; table-layout:fixed">
<colgroup>
<col style="width:40%">
<col style="width:60%">
</colgroup>
<tr>
<th>Event Name</th>
<th>Description</th>
@@ -1012,10 +1232,22 @@ _If you want to find diagnostic data events in the source code, these two links
<td>Microsoft.PowerToys.RegistryPreview_EnableRegistryPreview</td>
<td>Occurs when Registry Preview is enabled.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.RegistryPreviewEditorStartEvent</td>
<td>Triggered when the Registry Preview application starts. This logs the initialization of the Registry Preview UI with a timestamp.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.RegistryPreviewEditorStartFinishEvent</td>
<td>Triggered when the Registry Preview application has completed loading and is ready for user interaction.</td>
</tr>
</table>
### Screen Ruler
<table style="width:100%">
<table style="width:100%; table-layout:fixed">
<colgroup>
<col style="width:40%">
<col style="width:60%">
</colgroup>
<tr>
<th>Event Name</th>
<th>Description</th>
@@ -1035,7 +1267,11 @@ _If you want to find diagnostic data events in the source code, these two links
</table>
### Shortcut Guide
<table style="width:100%">
<table style="width:100%; table-layout:fixed">
<colgroup>
<col style="width:40%">
<col style="width:60%">
</colgroup>
<tr>
<th>Event Name</th>
<th>Description</th>
@@ -1051,7 +1287,11 @@ _If you want to find diagnostic data events in the source code, these two links
</table>
### Text Extractor
<table style="width:100%">
<table style="width:100%; table-layout:fixed">
<colgroup>
<col style="width:40%">
<col style="width:60%">
</colgroup>
<tr>
<th>Event Name</th>
<th>Description</th>
@@ -1075,15 +1315,15 @@ _If you want to find diagnostic data events in the source code, these two links
</table>
### Workspaces
<table style="width:100%">
<table style="width:100%; table-layout:fixed">
<colgroup>
<col style="width:40%">
<col style="width:60%">
</colgroup>
<tr>
<th>Event Name</th>
<th>Description</th>
</tr>
<tr>
<td>Microsoft.PowerToys.Projects_CLIUsage</td>
<td>Logs usage of command-line arguments for launching apps.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.Workspaces_CreateEvent</td>
<td>Triggered when a new workspace is created.</td>
@@ -1105,13 +1345,21 @@ _If you want to find diagnostic data events in the source code, these two links
<td>Triggered when a workspace is launched.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.Workspaces_Settings</td>
<td>Logs changes to workspaces settings.</td>
<td>Microsoft.PowerToys.WorkspacesEditorStartEvent</td>
<td>Triggered when the Workspaces Editor application starts. This logs the initialization of the Workspaces Editor UI with a timestamp.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.WorkspacesEditorStartFinishEvent</td>
<td>Triggered when the Workspaces Editor has completed loading and is ready for user interaction.</td>
</tr>
</table>
### ZoomIt
<table style="width:100%">
<table style="width:100%; table-layout:fixed">
<colgroup>
<col style="width:40%">
<col style="width:60%">
</colgroup>
<tr>
<th>Event Name</th>
<th>Description</th>

View File

@@ -1,226 +0,0 @@
# Settings UI Modernization Guide
This document describes the modernization patterns implemented in the PowerToys Settings UI to improve startup performance and maintainability.
## Overview
The Settings UI has been modernized with the following improvements:
1. **Dependency Injection (DI)** - Microsoft.Extensions.DependencyInjection for service resolution
2. **Page Caching** - Navigation caching to avoid page reconstruction
3. **Async ViewModel Initialization** - Non-blocking startup with IAsyncInitializable pattern
4. **Optimized Search** - ReaderWriterLockSlim for concurrent access
## Dependency Injection
### Configuration
Services are configured in `Services/AppServices.cs`:
```csharp
public static void Configure(Action<IServiceCollection> configureServices = null)
{
var services = new ServiceCollection();
ConfigureCoreServices(services);
configureServices?.Invoke(services);
_serviceProvider = services.BuildServiceProvider();
}
```
### Registered Services
| Interface | Implementation | Lifetime |
|-----------|----------------|----------|
| `INavigationService` | `NavigationServiceAdapter` | Singleton |
| `ISettingsService` | `SettingsService` | Singleton |
| `IIPCService` | `IPCService` | Singleton |
| `ViewModelLocator` | `ViewModelLocator` | Singleton |
### Usage
To resolve services:
```csharp
// In App.xaml.cs or any code
var navigationService = App.GetService<INavigationService>();
// Or directly from AppServices
var settingsService = AppServices.GetService<ISettingsService>();
```
## Page Caching
Pages are cached to avoid reconstruction on every navigation.
### Configuration
1. **Frame.CacheSize** - Set in `ShellPage.xaml.cs`:
```csharp
navigationView.Frame.CacheSize = 10;
```
2. **NavigationCacheMode** - Enabled in `NavigablePage` base class:
```csharp
protected NavigablePage()
{
NavigationCacheMode = NavigationCacheMode.Enabled;
}
```
All pages inheriting from `NavigablePage` automatically get caching.
## Async ViewModel Initialization
### IAsyncInitializable Interface
```csharp
public interface IAsyncInitializable
{
bool IsInitialized { get; }
bool IsLoading { get; }
Task InitializeAsync(CancellationToken cancellationToken = default);
}
```
### PageViewModelBase Implementation
The base ViewModel class implements this pattern:
```csharp
public abstract class PageViewModelBase : IAsyncInitializable
{
public bool IsInitialized { get; private set; }
public bool IsLoading { get; private set; }
public async Task InitializeAsync(CancellationToken cancellationToken = default)
{
if (IsInitialized) return;
IsLoading = true;
try
{
await InitializeCoreAsync(cancellationToken);
IsInitialized = true;
}
finally
{
IsLoading = false;
}
}
protected virtual Task InitializeCoreAsync(CancellationToken cancellationToken = default)
{
return Task.CompletedTask;
}
}
```
### Usage in ViewModels
Override `InitializeCoreAsync()` for async initialization:
```csharp
public class DashboardViewModel : PageViewModelBase
{
protected override async Task InitializeCoreAsync(CancellationToken cancellationToken = default)
{
await Task.Run(() =>
{
// Heavy initialization work
BuildModuleList();
}, cancellationToken);
}
}
```
## Search Service Optimization
### ReaderWriterLockSlim
The `SearchIndexService` uses `ReaderWriterLockSlim` for concurrent access:
- **Read operations**: Use `EnterReadLock()` for cache lookups
- **Write operations**: Use `EnterWriteLock()` for cache mutations
```csharp
private static readonly ReaderWriterLockSlim _cacheLock = new(LockRecursionPolicy.SupportsRecursion);
public static List<SettingEntry> Search(string query)
{
_cacheLock.EnterReadLock();
try
{
// Read from cache
}
finally
{
_cacheLock.ExitReadLock();
}
}
```
### Async Index Building
Search index is built asynchronously after first paint:
```csharp
private void ShellPage_Loaded(object sender, RoutedEventArgs e)
{
_ = SearchIndexService.BuildIndexAsync();
}
```
## Migration Guide
### Migrating Existing Pages
1. **Ensure page inherits from NavigablePage** (for caching)
2. **Update ViewModel to use InitializeCoreAsync()** for heavy initialization
3. **Replace static service calls** with DI-resolved services where feasible
### Example Migration
Before:
```csharp
public class MyViewModel
{
public MyViewModel()
{
// Heavy sync initialization
LoadSettings();
BuildList();
}
}
```
After:
```csharp
public class MyViewModel : PageViewModelBase
{
protected override string ModuleName => "MyModule";
protected override async Task InitializeCoreAsync(CancellationToken cancellationToken = default)
{
await Task.Run(() =>
{
LoadSettings();
BuildList();
}, cancellationToken);
}
}
```
## Performance Metrics
| Metric | Before | After |
|--------|--------|-------|
| Dashboard → General navigation | ~500-800ms | <100ms (cached) |
| Search during navigation | Blocked | Concurrent |
| App startup | Blocking | Non-blocking init |
## Future Work
- [ ] Migrate remaining 30+ pages to new patterns
- [ ] Add loading indicators during async initialization
- [ ] Further reduce GeneralViewModel constructor parameters (13 → 4)
- [ ] Add startup telemetry for performance monitoring

View File

@@ -18,6 +18,7 @@
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
<RegistryValue Type="string" Name="Module_CmdPal" Value="" KeyPath="yes" />
</RegistryKey>
<RemoveFile Id="RemoveOldCmdPalMsix" Name="Microsoft.CmdPal.UI_*.msix" On="install" />
<?if $(sys.BUILDARCH) = x64 ?>
<File Id="Microsoft.CmdPal.UI___var.CmdPalVersion_._x64.msix" Source="$(var.CmdPalBuildDir)AppPackages\Microsoft.CmdPal.UI_$(var.CmdPalVersion)_Test\Microsoft.CmdPal.UI_$(var.CmdPalVersion)_x64.msix" />
<?else?>

View File

@@ -16,13 +16,50 @@ DWORD WINAPI _checkTheme(LPVOID lpParam)
void ThemeListener::AddChangedHandler(THEME_HANDLE handle)
{
std::lock_guard<std::mutex> lock(handlesMutex);
handles.push_back(handle);
}
void ThemeListener::DelChangedHandler(THEME_HANDLE handle)
{
std::lock_guard<std::mutex> lock(handlesMutex);
auto it = std::find(handles.begin(), handles.end(), handle);
handles.erase(it);
if (it != handles.end())
{
handles.erase(it);
}
}
void ThemeListener::AddAppThemeChangedHandler(THEME_HANDLE handle)
{
std::lock_guard<std::mutex> lock(handlesMutex);
appThemeHandles.push_back(handle);
}
void ThemeListener::DelAppThemeChangedHandler(THEME_HANDLE handle)
{
std::lock_guard<std::mutex> lock(handlesMutex);
auto it = std::find(appThemeHandles.begin(), appThemeHandles.end(), handle);
if (it != appThemeHandles.end())
{
appThemeHandles.erase(it);
}
}
void ThemeListener::AddSystemThemeChangedHandler(THEME_HANDLE handle)
{
std::lock_guard<std::mutex> lock(handlesMutex);
systemThemeHandles.push_back(handle);
}
void ThemeListener::DelSystemThemeChangedHandler(THEME_HANDLE handle)
{
std::lock_guard<std::mutex> lock(handlesMutex);
auto it = std::find(systemThemeHandles.begin(), systemThemeHandles.end(), handle);
if (it != systemThemeHandles.end())
{
systemThemeHandles.erase(it);
}
}
void ThemeListener::CheckTheme()
@@ -48,13 +85,51 @@ void ThemeListener::CheckTheme()
WaitForSingleObject(hEvent, INFINITE);
auto _theme = ThemeHelpers::GetAppTheme();
if (AppTheme != _theme)
auto _appTheme = ThemeHelpers::GetAppTheme();
auto _systemTheme = ThemeHelpers::GetSystemTheme();
bool appThemeChanged = (AppTheme != _appTheme);
bool systemThemeChanged = (SystemTheme != _systemTheme);
if (appThemeChanged || systemThemeChanged)
{
AppTheme = _theme;
for (int i = 0; i < handles.size(); i++)
AppTheme = _appTheme;
SystemTheme = _systemTheme;
// Copy handlers under lock, then invoke outside lock to avoid deadlock
std::vector<THEME_HANDLE> handlesCopy;
std::vector<THEME_HANDLE> appThemeHandlesCopy;
std::vector<THEME_HANDLE> systemThemeHandlesCopy;
{
handles[i]();
std::lock_guard<std::mutex> lock(handlesMutex);
handlesCopy = handles;
if (appThemeChanged)
{
appThemeHandlesCopy = appThemeHandles;
}
if (systemThemeChanged)
{
systemThemeHandlesCopy = systemThemeHandles;
}
}
// Call generic handlers (backward compatible)
for (const auto& handler : handlesCopy)
{
handler();
}
// Call app theme specific handlers
for (const auto& handler : appThemeHandlesCopy)
{
handler();
}
// Call system theme specific handlers
for (const auto& handler : systemThemeHandlesCopy)
{
handler();
}
}
}

View File

@@ -3,6 +3,7 @@
#include <windows.h>
#include <iostream>
#include <vector>
#include <mutex>
typedef void (*THEME_HANDLE)();
DWORD WINAPI _checkTheme(LPVOID lpParam);
@@ -14,6 +15,7 @@ public:
ThemeListener()
{
AppTheme = ThemeHelpers::GetAppTheme();
SystemTheme = ThemeHelpers::GetSystemTheme();
dwThreadHandle = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)_checkTheme, this, 0, &dwThreadId);
}
~ThemeListener()
@@ -23,12 +25,20 @@ public:
}
Theme AppTheme;
Theme SystemTheme;
void ThemeListener::AddChangedHandler(THEME_HANDLE handle);
void ThemeListener::DelChangedHandler(THEME_HANDLE handle);
void ThemeListener::AddAppThemeChangedHandler(THEME_HANDLE handle);
void ThemeListener::DelAppThemeChangedHandler(THEME_HANDLE handle);
void ThemeListener::AddSystemThemeChangedHandler(THEME_HANDLE handle);
void ThemeListener::DelSystemThemeChangedHandler(THEME_HANDLE handle);
void CheckTheme();
private:
HANDLE dwThreadHandle;
DWORD dwThreadId;
std::vector<THEME_HANDLE> handles;
std::vector<THEME_HANDLE> appThemeHandles;
std::vector<THEME_HANDLE> systemThemeHandles;
mutable std::mutex handlesMutex;
};

View File

@@ -2,6 +2,7 @@
#include <Windows.h>
#include <algorithm>
#include <appxpackaging.h>
#include <exception>
#include <filesystem>
@@ -337,6 +338,30 @@ namespace package
}
}
}
// Sort by package version in descending order (newest first)
std::sort(matchedFiles.begin(), matchedFiles.end(), [](const std::wstring& a, const std::wstring& b) {
std::wstring nameA, nameB;
PACKAGE_VERSION versionA{}, versionB{};
bool gotA = GetPackageNameAndVersionFromAppx(a, nameA, versionA);
bool gotB = GetPackageNameAndVersionFromAppx(b, nameB, versionB);
// Files that failed to parse go to the end
if (!gotA)
return false;
if (!gotB)
return true;
// Compare versions: Major, Minor, Build, Revision (descending)
if (versionA.Major != versionB.Major)
return versionA.Major > versionB.Major;
if (versionA.Minor != versionB.Minor)
return versionA.Minor > versionB.Minor;
if (versionA.Build != versionB.Build)
return versionA.Build > versionB.Build;
return versionA.Revision > versionB.Revision;
});
}
catch (const std::exception& ex)
{

View File

@@ -98,18 +98,16 @@ public sealed partial class AppListItem : ListItem
}
else
{
try
// do nothing if we fail to load an icon.
// Logging it would be too NOISY, there's really no need.
if (!string.IsNullOrEmpty(_app.IcoPath))
{
var stream = await ThumbnailHelper.GetThumbnail(_app.ExePath, true);
if (stream is not null)
{
heroImage = IconInfo.FromStream(stream);
}
heroImage = await TryLoadThumbnail(_app.IcoPath, jumbo: true, logOnFailure: false);
}
catch (Exception)
if (heroImage == null && !string.IsNullOrEmpty(_app.ExePath))
{
// do nothing if we fail to load an icon.
// Logging it would be too NOISY, there's really no need.
heroImage = await TryLoadThumbnail(_app.ExePath, jumbo: true, logOnFailure: false);
}
}
@@ -132,26 +130,19 @@ public sealed partial class AppListItem : ListItem
if (useThumbnails)
{
try
if (!string.IsNullOrEmpty(_app.IcoPath))
{
var stream = await ThumbnailHelper.GetThumbnail(_app.ExePath);
if (stream is not null)
{
icon = IconInfo.FromStream(stream);
}
}
catch (Exception ex)
{
Logger.LogDebug($"Failed to load icon for {AppIdentifier}:\n{ex}");
icon = await TryLoadThumbnail(_app.IcoPath, jumbo: false, logOnFailure: true);
}
icon = icon ?? new IconInfo(_app.IcoPath);
}
else
{
icon = new IconInfo(_app.IcoPath);
if (icon == null && !string.IsNullOrEmpty(_app.ExePath))
{
icon = await TryLoadThumbnail(_app.ExePath, jumbo: false, logOnFailure: true);
}
}
icon ??= new IconInfo(_app.IcoPath);
return icon;
}
@@ -183,4 +174,25 @@ public sealed partial class AppListItem : ListItem
return newCommands.ToArray();
}
private async Task<IconInfo?> TryLoadThumbnail(string path, bool jumbo, bool logOnFailure)
{
try
{
var stream = await ThumbnailHelper.GetThumbnail(path, jumbo);
if (stream is not null)
{
return IconInfo.FromStream(stream);
}
}
catch (Exception ex)
{
if (logOnFailure)
{
Logger.LogDebug($"Failed to load icon {path} for {AppIdentifier}:\n{ex}");
}
}
return null;
}
}

View File

@@ -1052,9 +1052,6 @@ public class Win32Program : IProgram
app.FullPath) :
app.IcoPath;
icoPath = icoPath.EndsWith(".lnk", System.StringComparison.InvariantCultureIgnoreCase) ?
app.FullPath :
icoPath;
return new AppItem()
{
Name = app.Name,

View File

@@ -197,16 +197,10 @@ namespace Peek.UI
ViewModel.Initialize(selectedItem);
// If no files were found (e.g., in virtual folders like Home/Recent), show an error
// If no files were found (e.g., user is typing in rename/search box, or in virtual folders),
// don't show anything - just return silently to avoid stealing focus
if (ViewModel.CurrentItem == null)
{
Logger.LogInfo("Peek: No files found to preview, showing error.");
var errorMessage = ResourceLoaderInstance.ResourceLoader.GetString("NoFilesSelected");
ViewModel.ShowError(errorMessage);
// Still show the window so user can see the warning
this.Show();
WindowHelpers.BringToForeground(this.GetWindowHandle());
return;
}

View File

@@ -33,23 +33,27 @@ static std::wstring SanitizeAndNormalize(const std::wstring& input)
// Normalize to NFC (Precomposed).
// Get the size needed for the normalized string, including null terminator.
int size = NormalizeString(NormalizationC, sanitized.c_str(), -1, nullptr, 0);
if (size <= 0)
int sizeEstimate = NormalizeString(NormalizationC, sanitized.c_str(), -1, nullptr, 0);
if (sizeEstimate <= 0)
{
return sanitized; // Return unaltered if normalization fails.
}
// Perform the normalization.
std::wstring normalized;
normalized.resize(size);
NormalizeString(NormalizationC, sanitized.c_str(), -1, &normalized[0], size);
normalized.resize(sizeEstimate);
int actualSize = NormalizeString(NormalizationC, sanitized.c_str(), -1, &normalized[0], sizeEstimate);
// Remove the explicit null terminator added by NormalizeString.
if (!normalized.empty() && normalized.back() == L'\0')
if (actualSize <= 0)
{
normalized.pop_back();
// Normalization failed, return sanitized string.
return sanitized;
}
// Resize to actual size minus the null terminator.
// actualSize includes the null terminator when input length is -1.
normalized.resize(static_cast<size_t>(actualSize) - 1);
return normalized;
}

View File

@@ -695,6 +695,38 @@ TEST_METHOD(VerifyUnicodeAndWhitespaceNormalizationRegex)
VerifyNormalizationHelper(UseRegularExpressions);
}
TEST_METHOD(VerifyRegexMetacharacterDollarSign)
{
CComPtr<IPowerRenameRegEx> renameRegEx;
Assert::IsTrue(CPowerRenameRegEx::s_CreateInstance(&renameRegEx) == S_OK);
DWORD flags = UseRegularExpressions;
Assert::IsTrue(renameRegEx->PutFlags(flags) == S_OK);
PWSTR result = nullptr;
Assert::IsTrue(renameRegEx->PutSearchTerm(L"$") == S_OK);
Assert::IsTrue(renameRegEx->PutReplaceTerm(L"_end") == S_OK);
unsigned long index = {};
Assert::IsTrue(renameRegEx->Replace(L"test.txt", &result, index) == S_OK);
Assert::AreEqual(L"test.txt_end", result);
CoTaskMemFree(result);
}
TEST_METHOD(VerifyRegexMetacharacterCaret)
{
CComPtr<IPowerRenameRegEx> renameRegEx;
Assert::IsTrue(CPowerRenameRegEx::s_CreateInstance(&renameRegEx) == S_OK);
DWORD flags = UseRegularExpressions;
Assert::IsTrue(renameRegEx->PutFlags(flags) == S_OK);
PWSTR result = nullptr;
Assert::IsTrue(renameRegEx->PutSearchTerm(L"^") == S_OK);
Assert::IsTrue(renameRegEx->PutReplaceTerm(L"start_") == S_OK);
unsigned long index = {};
Assert::IsTrue(renameRegEx->Replace(L"test.txt", &result, index) == S_OK);
Assert::AreEqual(L"start_test.txt", result);
CoTaskMemFree(result);
}
#ifndef TESTS_PARTIAL
};
}

View File

@@ -173,6 +173,10 @@
<value>Settings\tDouble-click</value>
<comment>Don't localize "\t" as that is what separates the click portion to be right aligned in the menu.</comment>
</data>
<data name="SETTINGS_MENU_TEXT_LEFTCLICK" xml:space="preserve">
<value>Settings\tLeft-click</value>
<comment>Don't localize "\t" as that is what separates the click portion to be right aligned in the menu. This is shown when Quick Access is disabled.</comment>
</data>
<data name="DOCUMENTATION_MENU_TEXT" xml:space="preserve">
<value>Documentation</value>
</data>

View File

@@ -81,3 +81,36 @@ void Trace::UpdateDownloadCompleted(bool success, const std::wstring& version)
TraceLoggingBoolean(TRUE, "UTCReplace_AppSessionGuid"),
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE));
}
void Trace::TrayIconLeftClick(bool quickAccessEnabled)
{
TraceLoggingWriteWrapper(
g_hProvider,
"TrayIcon_LeftClick",
TraceLoggingBoolean(quickAccessEnabled, "QuickAccessEnabled"),
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
TraceLoggingBoolean(TRUE, "UTCReplace_AppSessionGuid"),
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE));
}
void Trace::TrayIconDoubleClick(bool quickAccessEnabled)
{
TraceLoggingWriteWrapper(
g_hProvider,
"TrayIcon_DoubleClick",
TraceLoggingBoolean(quickAccessEnabled, "QuickAccessEnabled"),
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
TraceLoggingBoolean(TRUE, "UTCReplace_AppSessionGuid"),
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE));
}
void Trace::TrayIconRightClick(bool quickAccessEnabled)
{
TraceLoggingWriteWrapper(
g_hProvider,
"TrayIcon_RightClick",
TraceLoggingBoolean(quickAccessEnabled, "QuickAccessEnabled"),
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
TraceLoggingBoolean(TRUE, "UTCReplace_AppSessionGuid"),
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE));
}

View File

@@ -13,4 +13,9 @@ public:
// Auto-update telemetry
static void UpdateCheckCompleted(bool success, bool updateAvailable, const std::wstring& fromVersion, const std::wstring& toVersion);
static void UpdateDownloadCompleted(bool success, const std::wstring& version);
// Tray icon interaction telemetry
static void TrayIconLeftClick(bool quickAccessEnabled);
static void TrayIconDoubleClick(bool quickAccessEnabled);
static void TrayIconRightClick(bool quickAccessEnabled);
};

View File

@@ -7,6 +7,7 @@
#include "centralized_kb_hook.h"
#include "quick_access_host.h"
#include "hotkey_conflict_detector.h"
#include "trace.h"
#include <Windows.h>
#include <common/utils/resources.h>
@@ -14,6 +15,7 @@
#include <common/logger/logger.h>
#include <common/utils/elevation.h>
#include <common/Themes/theme_listener.h>
#include <common/Themes/theme_helpers.h>
#include "bug_report.h"
namespace
@@ -39,6 +41,7 @@ namespace
bool double_click_timer_running = false;
bool double_clicked = false;
POINT tray_icon_click_point;
std::optional<bool> last_quick_access_state; // Track the last known Quick Access state
static ThemeListener theme_listener;
static bool theme_adaptive_enabled = false;
@@ -129,6 +132,9 @@ void click_timer_elapsed()
double_click_timer_running = false;
if (!double_clicked)
{
// Log telemetry for single click (confirmed it's not a double click)
Trace::TrayIconLeftClick(get_general_settings().enableQuickAccess);
if (get_general_settings().enableQuickAccess)
{
open_quick_access_flyout_window();
@@ -194,6 +200,21 @@ LRESULT __stdcall tray_icon_window_proc(HWND window, UINT message, WPARAM wparam
case WM_RBUTTONUP:
case WM_CONTEXTMENU:
{
bool quick_access_enabled = get_general_settings().enableQuickAccess;
// Log telemetry
Trace::TrayIconRightClick(quick_access_enabled);
// Reload menu if Quick Access state has changed or is first time
if (h_menu && (!last_quick_access_state.has_value() || quick_access_enabled != last_quick_access_state.value()))
{
DestroyMenu(h_menu);
h_menu = nullptr;
h_sub_menu = nullptr;
}
last_quick_access_state = quick_access_enabled;
if (!h_menu)
{
h_menu = LoadMenu(reinterpret_cast<HINSTANCE>(&__ImageBase), MAKEINTRESOURCE(ID_TRAY_MENU));
@@ -201,17 +222,39 @@ LRESULT __stdcall tray_icon_window_proc(HWND window, UINT message, WPARAM wparam
if (h_menu)
{
static std::wstring settings_menuitem_label = GET_RESOURCE_STRING(IDS_SETTINGS_MENU_TEXT);
static std::wstring settings_menuitem_label_leftclick = GET_RESOURCE_STRING(IDS_SETTINGS_MENU_TEXT_LEFTCLICK);
static std::wstring close_menuitem_label = GET_RESOURCE_STRING(IDS_CLOSE_MENU_TEXT);
static std::wstring submit_bug_menuitem_label = GET_RESOURCE_STRING(IDS_SUBMIT_BUG_TEXT);
static std::wstring documentation_menuitem_label = GET_RESOURCE_STRING(IDS_DOCUMENTATION_MENU_TEXT);
static std::wstring quick_access_menuitem_label = GET_RESOURCE_STRING(IDS_QUICK_ACCESS_MENU_TEXT);
change_menu_item_text(ID_SETTINGS_MENU_COMMAND, settings_menuitem_label.data());
// Update Settings menu text based on Quick Access state
if (quick_access_enabled)
{
change_menu_item_text(ID_SETTINGS_MENU_COMMAND, settings_menuitem_label.data());
}
else
{
change_menu_item_text(ID_SETTINGS_MENU_COMMAND, settings_menuitem_label_leftclick.data());
}
change_menu_item_text(ID_CLOSE_MENU_COMMAND, close_menuitem_label.data());
change_menu_item_text(ID_REPORT_BUG_COMMAND, submit_bug_menuitem_label.data());
bool bug_report_disabled = is_bug_report_running();
EnableMenuItem(h_sub_menu, ID_REPORT_BUG_COMMAND, MF_BYCOMMAND | (bug_report_disabled ? MF_GRAYED : MF_ENABLED));
change_menu_item_text(ID_DOCUMENTATION_MENU_COMMAND, documentation_menuitem_label.data());
change_menu_item_text(ID_QUICK_ACCESS_MENU_COMMAND, quick_access_menuitem_label.data());
// Hide or show Quick Access menu item based on setting
if (!h_sub_menu)
{
h_sub_menu = GetSubMenu(h_menu, 0);
}
if (!quick_access_enabled)
{
// Remove Quick Access menu item when disabled
DeleteMenu(h_sub_menu, ID_QUICK_ACCESS_MENU_COMMAND, MF_BYCOMMAND);
}
}
if (!h_sub_menu)
{
@@ -242,6 +285,9 @@ LRESULT __stdcall tray_icon_window_proc(HWND window, UINT message, WPARAM wparam
}
case WM_LBUTTONDBLCLK:
{
// Log telemetry
Trace::TrayIconDoubleClick(get_general_settings().enableQuickAccess);
double_clicked = true;
open_settings_window(std::nullopt);
break;
@@ -293,7 +339,7 @@ static void handle_theme_change()
{
if (theme_adaptive_enabled)
{
tray_icon_data.hIcon = get_icon(theme_listener.AppTheme);
tray_icon_data.hIcon = get_icon(ThemeHelpers::GetSystemTheme());
Shell_NotifyIcon(NIM_MODIFY, &tray_icon_data);
}
}
@@ -310,7 +356,7 @@ void start_tray_icon(bool isProcessElevated, bool theme_adaptive)
{
theme_adaptive_enabled = theme_adaptive;
auto h_instance = reinterpret_cast<HINSTANCE>(&__ImageBase);
HICON const icon = theme_adaptive ? get_icon(theme_listener.AppTheme) : LoadIcon(h_instance, MAKEINTRESOURCE(APPICON));
HICON const icon = theme_adaptive ? get_icon(ThemeHelpers::GetSystemTheme()) : LoadIcon(h_instance, MAKEINTRESOURCE(APPICON));
if (icon)
{
UINT id_tray_icon = 1;
@@ -357,7 +403,7 @@ void start_tray_icon(bool isProcessElevated, bool theme_adaptive)
ChangeWindowMessageFilterEx(hwnd, WM_COMMAND, MSGFLT_ALLOW, nullptr);
tray_icon_created = Shell_NotifyIcon(NIM_ADD, &tray_icon_data) == TRUE;
theme_listener.AddChangedHandler(&handle_theme_change);
theme_listener.AddSystemThemeChangedHandler(&handle_theme_change);
// Register callback to update bug report menu item status
BugReportManager::instance().register_callback([](bool isRunning) {
@@ -389,7 +435,7 @@ void set_tray_icon_theme_adaptive(bool theme_adaptive)
if (theme_adaptive)
{
icon = get_icon(theme_listener.AppTheme);
icon = get_icon(ThemeHelpers::GetSystemTheme());
if (!icon)
{
Logger::warn(L"set_tray_icon_theme_adaptive: Failed to load theme adaptive icon, falling back to default");

View File

@@ -34,21 +34,21 @@ public sealed class AdvancedPastePasteAsFileAction : Observable, IAdvancedPasteA
public AdvancedPasteAdditionalAction PasteAsTxtFile
{
get => _pasteAsTxtFile;
init => Set(ref _pasteAsTxtFile, value);
init => Set(ref _pasteAsTxtFile, value ?? new());
}
[JsonPropertyName(PropertyNames.PasteAsPngFile)]
public AdvancedPasteAdditionalAction PasteAsPngFile
{
get => _pasteAsPngFile;
init => Set(ref _pasteAsPngFile, value);
init => Set(ref _pasteAsPngFile, value ?? new());
}
[JsonPropertyName(PropertyNames.PasteAsHtmlFile)]
public AdvancedPasteAdditionalAction PasteAsHtmlFile
{
get => _pasteAsHtmlFile;
init => Set(ref _pasteAsHtmlFile, value);
init => Set(ref _pasteAsHtmlFile, value ?? new());
}
[JsonIgnore]

View File

@@ -93,11 +93,11 @@ namespace Microsoft.PowerToys.Settings.UI.Library
[JsonPropertyName("custom-actions")]
[CmdConfigureIgnoreAttribute]
public AdvancedPasteCustomActions CustomActions { get; init; }
public AdvancedPasteCustomActions CustomActions { get; set; }
[JsonPropertyName("additional-actions")]
[CmdConfigureIgnoreAttribute]
public AdvancedPasteAdditionalActions AdditionalActions { get; init; }
public AdvancedPasteAdditionalActions AdditionalActions { get; set; }
[JsonPropertyName("paste-ai-configuration")]
[CmdConfigureIgnoreAttribute]

View File

@@ -32,14 +32,14 @@ public sealed class AdvancedPasteTranscodeAction : Observable, IAdvancedPasteAct
public AdvancedPasteAdditionalAction TranscodeToMp3
{
get => _transcodeToMp3;
init => Set(ref _transcodeToMp3, value);
init => Set(ref _transcodeToMp3, value ?? new());
}
[JsonPropertyName(PropertyNames.TranscodeToMp4)]
public AdvancedPasteAdditionalAction TranscodeToMp4
{
get => _transcodeToMp4;
init => Set(ref _transcodeToMp4, value);
init => Set(ref _transcodeToMp4, value ?? new());
}
[JsonIgnore]

View File

@@ -1,53 +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.PowerToys.Settings.UI.Services;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Microsoft.PowerToys.Settings.UI.UnitTests.ServiceTests
{
[TestClass]
public class AppServicesTests
{
[TestMethod]
public void AppServices_Configure_SetsIsConfiguredToTrue()
{
// Note: AppServices is static and may already be configured from other tests
// This test verifies the configuration doesn't throw
AppServices.Configure();
Assert.IsTrue(AppServices.IsConfigured);
}
[TestMethod]
public void AppServices_GetService_ReturnsNavigationService()
{
AppServices.Configure();
var navigationService = AppServices.GetService<INavigationService>();
Assert.IsNotNull(navigationService);
}
[TestMethod]
public void AppServices_GetService_ReturnsSettingsService()
{
AppServices.Configure();
var settingsService = AppServices.GetService<ISettingsService>();
Assert.IsNotNull(settingsService);
}
[TestMethod]
public void AppServices_GetService_ReturnsIPCService()
{
AppServices.Configure();
var ipcService = AppServices.GetService<IIPCService>();
Assert.IsNotNull(ipcService);
}
}
}

View File

@@ -1,77 +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.Threading;
using System.Threading.Tasks;
using Microsoft.PowerToys.Settings.UI.ViewModels;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Microsoft.PowerToys.Settings.UI.UnitTests.ServiceTests
{
[TestClass]
public class AsyncInitializableTests
{
private sealed class TestViewModel : PageViewModelBase
{
protected override string ModuleName => "TestModule";
public bool InitializeCoreWasCalled { get; set; }
protected override Task InitializeCoreAsync(CancellationToken cancellationToken = default)
{
InitializeCoreWasCalled = true;
return Task.CompletedTask;
}
}
[TestMethod]
public async Task InitializeAsync_SetsIsInitializedToTrue()
{
var viewModel = new TestViewModel();
await viewModel.InitializeAsync();
Assert.IsTrue(viewModel.IsInitialized);
}
[TestMethod]
public async Task InitializeAsync_CallsInitializeCoreAsync()
{
var viewModel = new TestViewModel();
await viewModel.InitializeAsync();
Assert.IsTrue(viewModel.InitializeCoreWasCalled);
}
[TestMethod]
public async Task InitializeAsync_DoesNotReinitializeIfAlreadyInitialized()
{
var viewModel = new TestViewModel();
await viewModel.InitializeAsync();
viewModel.InitializeCoreWasCalled = false; // Reset the flag
await viewModel.InitializeAsync(); // Should not call InitializeCoreAsync again
// InitializeCoreWasCalled should still be false since we reset it
// and InitializeAsync should skip if already initialized
Assert.IsFalse(viewModel.InitializeCoreWasCalled);
}
[TestMethod]
public async Task InitializeAsync_SetsIsLoadingDuringInitialization()
{
var viewModel = new TestViewModel();
// IsLoading should be false before initialization
Assert.IsFalse(viewModel.IsLoading);
await viewModel.InitializeAsync();
// IsLoading should be false after initialization completes
Assert.IsFalse(viewModel.IsLoading);
}
}
}

View File

@@ -23,10 +23,6 @@ public abstract partial class NavigablePage : Page
public NavigablePage()
{
// Enable navigation caching by default for better performance.
// This prevents pages from being recreated on every navigation.
NavigationCacheMode = Microsoft.UI.Xaml.Navigation.NavigationCacheMode.Enabled;
Loaded += OnPageLoaded;
}

View File

@@ -73,9 +73,6 @@
</ItemGroup>
<ItemGroup>
<!-- MVVM and DI Infrastructure -->
<PackageReference Include="CommunityToolkit.Mvvm" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
<PackageReference Include="CommunityToolkit.Labs.WinUI.Controls.OpacityMaskView" />
<PackageReference Include="CommunityToolkit.WinUI.Controls.Segmented" />
<PackageReference Include="CommunityToolkit.WinUI.Controls.SettingsControls" />

View File

@@ -1,119 +0,0 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.PowerToys.Settings.UI.Library;
namespace Microsoft.PowerToys.Settings.UI.Services
{
/// <summary>
/// Central service provider for the application's dependency injection container.
/// </summary>
public static class AppServices
{
private static readonly object _lock = new object();
private static IServiceProvider _serviceProvider;
/// <summary>
/// Gets the service provider instance.
/// </summary>
public static IServiceProvider ServiceProvider
{
get
{
if (_serviceProvider == null)
{
throw new InvalidOperationException(
"ServiceProvider has not been initialized. Call Configure() during app startup.");
}
return _serviceProvider;
}
}
/// <summary>
/// Gets a value indicating whether the service provider has been configured.
/// </summary>
public static bool IsConfigured => _serviceProvider != null;
/// <summary>
/// Configures the dependency injection container with all required services.
/// This should be called once during application startup.
/// </summary>
/// <param name="configureServices">Optional action to add additional services.</param>
public static void Configure(Action<IServiceCollection> configureServices = null)
{
lock (_lock)
{
if (_serviceProvider != null)
{
return; // Already configured
}
var services = new ServiceCollection();
// Register core services
ConfigureCoreServices(services);
// Allow additional configuration
configureServices?.Invoke(services);
_serviceProvider = services.BuildServiceProvider();
}
}
/// <summary>
/// Gets a service of type T from the DI container.
/// </summary>
/// <typeparam name="T">The type of service to get.</typeparam>
/// <returns>The service instance, or null if not registered.</returns>
public static T GetService<T>()
where T : class
{
return ServiceProvider.GetService<T>();
}
/// <summary>
/// Gets a required service of type T from the DI container.
/// Throws if the service is not registered.
/// </summary>
/// <typeparam name="T">The type of service to get.</typeparam>
/// <returns>The service instance.</returns>
public static T GetRequiredService<T>()
where T : class
{
return ServiceProvider.GetRequiredService<T>();
}
private static void ConfigureCoreServices(IServiceCollection services)
{
// Navigation service - singleton for app-wide navigation
services.AddSingleton<INavigationService, NavigationServiceAdapter>();
// Settings utilities - singleton
services.AddSingleton(_ => SettingsUtils.Default);
// Settings service - singleton for centralized settings management
services.AddSingleton<ISettingsService, SettingsService>();
// IPC service - singleton for inter-process communication
services.AddSingleton<IIPCService, IPCService>();
// General settings repository - singleton to share settings across pages
services.AddSingleton(sp =>
{
var settingsUtils = sp.GetRequiredService<SettingsUtils>();
return SettingsRepository<GeneralSettings>.GetInstance(settingsUtils);
});
// Resource loader - singleton
services.AddSingleton(_ => Helpers.ResourceLoaderInstance.ResourceLoader);
// ViewModelLocator for XAML binding (future use)
services.AddSingleton(sp => new ViewModelLocator(sp, enableCaching: false));
}
}
}

View File

@@ -1,63 +0,0 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Threading.Tasks;
using Windows.Data.Json;
namespace Microsoft.PowerToys.Settings.UI.Services
{
/// <summary>
/// Interface for Inter-Process Communication with the PowerToys Runner.
/// Abstracts the static IPC callbacks to enable dependency injection and testability.
/// </summary>
public interface IIPCService
{
/// <summary>
/// Gets a value indicating whether IPC is connected to the Runner.
/// </summary>
bool IsConnected { get; }
/// <summary>
/// Sends a default IPC message to the Runner.
/// </summary>
/// <param name="message">The JSON message to send.</param>
/// <returns>Result code from the IPC call.</returns>
int SendMessage(string message);
/// <summary>
/// Sends a request to restart as administrator.
/// </summary>
/// <param name="message">The JSON message to send.</param>
/// <returns>Result code from the IPC call.</returns>
int SendRestartAsAdminMessage(string message);
/// <summary>
/// Sends a request to check for updates.
/// </summary>
/// <param name="message">The JSON message to send.</param>
/// <returns>Result code from the IPC call.</returns>
int SendCheckForUpdatesMessage(string message);
/// <summary>
/// Sends a message asynchronously.
/// </summary>
/// <param name="message">The JSON message to send.</param>
/// <returns>A task representing the async operation with result code.</returns>
Task<int> SendMessageAsync(string message);
/// <summary>
/// Registers a callback for IPC responses.
/// </summary>
/// <param name="callback">The callback to invoke when a response is received.</param>
void RegisterResponseCallback(Action<JsonObject> callback);
/// <summary>
/// Unregisters a previously registered callback.
/// </summary>
/// <param name="callback">The callback to unregister.</param>
void UnregisterResponseCallback(Action<JsonObject> callback);
}
}

View File

@@ -1,94 +0,0 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Threading.Tasks;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media.Animation;
using Microsoft.UI.Xaml.Navigation;
namespace Microsoft.PowerToys.Settings.UI.Services
{
/// <summary>
/// Interface for navigation service that supports async navigation and page caching.
/// </summary>
public interface INavigationService
{
/// <summary>
/// Raised when navigation has completed.
/// </summary>
event NavigatedEventHandler Navigated;
/// <summary>
/// Raised when navigation has failed.
/// </summary>
event NavigationFailedEventHandler NavigationFailed;
/// <summary>
/// Gets or sets the navigation frame.
/// </summary>
Frame Frame { get; set; }
/// <summary>
/// Gets a value indicating whether we can navigate back.
/// </summary>
bool CanGoBack { get; }
/// <summary>
/// Gets a value indicating whether we can navigate forward.
/// </summary>
bool CanGoForward { get; }
/// <summary>
/// Navigates back in the navigation stack.
/// </summary>
/// <returns>True if navigation was successful.</returns>
bool GoBack();
/// <summary>
/// Navigates forward in the navigation stack.
/// </summary>
void GoForward();
/// <summary>
/// Navigates to a page synchronously.
/// </summary>
/// <param name="pageType">The type of page to navigate to.</param>
/// <param name="parameter">Optional navigation parameter.</param>
/// <param name="infoOverride">Optional navigation transition.</param>
/// <returns>True if navigation was successful.</returns>
bool Navigate(Type pageType, object parameter = null, NavigationTransitionInfo infoOverride = null);
/// <summary>
/// Navigates to a page asynchronously, waiting for ViewModel initialization.
/// </summary>
/// <param name="pageType">The type of page to navigate to.</param>
/// <param name="parameter">Optional navigation parameter.</param>
/// <param name="infoOverride">Optional navigation transition.</param>
/// <returns>A task that completes when navigation and initialization are done.</returns>
Task<bool> NavigateAsync(Type pageType, object parameter = null, NavigationTransitionInfo infoOverride = null);
/// <summary>
/// Navigates to a page synchronously using generics.
/// </summary>
/// <typeparam name="T">The type of page to navigate to.</typeparam>
/// <param name="parameter">Optional navigation parameter.</param>
/// <param name="infoOverride">Optional navigation transition.</param>
/// <returns>True if navigation was successful.</returns>
bool Navigate<T>(object parameter = null, NavigationTransitionInfo infoOverride = null)
where T : Page;
/// <summary>
/// Ensures a page is selected when the frame content is null.
/// </summary>
/// <param name="pageType">The type of page to navigate to if nothing is selected.</param>
void EnsurePageIsSelected(Type pageType);
/// <summary>
/// Clears the navigation back stack.
/// </summary>
void ClearBackStack();
}
}

View File

@@ -1,91 +0,0 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.PowerToys.Settings.UI.Views;
using Windows.Data.Json;
namespace Microsoft.PowerToys.Settings.UI.Services
{
/// <summary>
/// Implementation of IIPCService that wraps the static ShellPage IPC methods.
/// This adapter allows for dependency injection while maintaining backward compatibility.
/// </summary>
public class IPCService : IIPCService
{
private readonly List<Action<JsonObject>> _responseCallbacks = new();
private readonly object _callbackLock = new();
/// <inheritdoc/>
public bool IsConnected => ShellPage.DefaultSndMSGCallback != null;
/// <inheritdoc/>
public int SendMessage(string message)
{
return ShellPage.SendDefaultIPCMessage(message);
}
/// <inheritdoc/>
public int SendRestartAsAdminMessage(string message)
{
return ShellPage.SendRestartAdminIPCMessage(message);
}
/// <inheritdoc/>
public int SendCheckForUpdatesMessage(string message)
{
return ShellPage.SendCheckForUpdatesIPCMessage(message);
}
/// <inheritdoc/>
public Task<int> SendMessageAsync(string message)
{
return Task.Run(() => SendMessage(message));
}
/// <inheritdoc/>
public void RegisterResponseCallback(Action<JsonObject> callback)
{
if (callback == null)
{
return;
}
lock (_callbackLock)
{
if (!_responseCallbacks.Contains(callback))
{
_responseCallbacks.Add(callback);
// Also register with ShellPage for backward compatibility
if (ShellPage.ShellHandler?.IPCResponseHandleList != null)
{
ShellPage.ShellHandler.IPCResponseHandleList.Add(callback);
}
}
}
}
/// <inheritdoc/>
public void UnregisterResponseCallback(Action<JsonObject> callback)
{
if (callback == null)
{
return;
}
lock (_callbackLock)
{
_responseCallbacks.Remove(callback);
// Also unregister from ShellPage
ShellPage.ShellHandler?.IPCResponseHandleList?.Remove(callback);
}
}
}
}

View File

@@ -1,69 +0,0 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
namespace Microsoft.PowerToys.Settings.UI.Services
{
/// <summary>
/// Interface for settings management with async loading and caching support.
/// </summary>
public interface ISettingsService
{
/// <summary>
/// Gets the general settings repository.
/// </summary>
ISettingsRepository<GeneralSettings> GeneralSettingsRepository { get; }
/// <summary>
/// Gets the general settings configuration.
/// </summary>
GeneralSettings GeneralSettings { get; }
/// <summary>
/// Gets the settings utilities instance.
/// </summary>
SettingsUtils SettingsUtils { get; }
/// <summary>
/// Gets a value indicating whether settings have been loaded.
/// </summary>
bool IsLoaded { get; }
/// <summary>
/// Loads settings asynchronously.
/// </summary>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A task representing the async operation.</returns>
Task LoadAsync(CancellationToken cancellationToken = default);
/// <summary>
/// Gets a settings repository for the specified settings type.
/// </summary>
/// <typeparam name="T">The type of settings.</typeparam>
/// <returns>The settings repository.</returns>
ISettingsRepository<T> GetRepository<T>()
where T : class, ISettingsConfig, new();
/// <summary>
/// Saves settings asynchronously.
/// </summary>
/// <typeparam name="T">The type of settings.</typeparam>
/// <param name="settings">The settings to save.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A task representing the async operation.</returns>
Task SaveAsync<T>(T settings, CancellationToken cancellationToken = default)
where T : class, ISettingsConfig, new();
/// <summary>
/// Raised when settings are externally changed.
/// </summary>
event Action<GeneralSettings> GeneralSettingsChanged;
}
}

View File

@@ -1,108 +0,0 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Threading.Tasks;
using Microsoft.PowerToys.Settings.UI.ViewModels;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media.Animation;
using Microsoft.UI.Xaml.Navigation;
namespace Microsoft.PowerToys.Settings.UI.Services
{
/// <summary>
/// Adapter that wraps the static NavigationService to implement INavigationService.
/// This allows for dependency injection while maintaining backward compatibility.
/// </summary>
public class NavigationServiceAdapter : INavigationService
{
private const int DefaultCacheSize = 10;
/// <inheritdoc/>
public event NavigatedEventHandler Navigated
{
add => NavigationService.Navigated += value;
remove => NavigationService.Navigated -= value;
}
/// <inheritdoc/>
public event NavigationFailedEventHandler NavigationFailed
{
add => NavigationService.NavigationFailed += value;
remove => NavigationService.NavigationFailed -= value;
}
/// <inheritdoc/>
public Frame Frame
{
get => NavigationService.Frame;
set
{
NavigationService.Frame = value;
// Enable page caching for better navigation performance
if (value != null && value.CacheSize < DefaultCacheSize)
{
value.CacheSize = DefaultCacheSize;
}
}
}
/// <inheritdoc/>
public bool CanGoBack => NavigationService.CanGoBack;
/// <inheritdoc/>
public bool CanGoForward => NavigationService.CanGoForward;
/// <inheritdoc/>
public bool GoBack() => NavigationService.GoBack();
/// <inheritdoc/>
public void GoForward() => NavigationService.GoForward();
/// <inheritdoc/>
public bool Navigate(Type pageType, object parameter = null, NavigationTransitionInfo infoOverride = null)
{
return NavigationService.Navigate(pageType, parameter, infoOverride);
}
/// <inheritdoc/>
public async Task<bool> NavigateAsync(Type pageType, object parameter = null, NavigationTransitionInfo infoOverride = null)
{
var result = NavigationService.Navigate(pageType, parameter, infoOverride);
if (result && Frame?.Content is FrameworkElement element)
{
// If the page's DataContext implements IAsyncInitializable, await its initialization
if (element.DataContext is IAsyncInitializable asyncInit && !asyncInit.IsInitialized)
{
await asyncInit.InitializeAsync();
}
}
return result;
}
/// <inheritdoc/>
public bool Navigate<T>(object parameter = null, NavigationTransitionInfo infoOverride = null)
where T : Page
{
return NavigationService.Navigate<T>(parameter, infoOverride);
}
/// <inheritdoc/>
public void EnsurePageIsSelected(Type pageType)
{
NavigationService.EnsurePageIsSelected(pageType);
}
/// <inheritdoc/>
public void ClearBackStack()
{
Frame?.BackStack.Clear();
}
}
}

View File

@@ -25,8 +25,7 @@ namespace Microsoft.PowerToys.Settings.UI.Services
{
public static class SearchIndexService
{
// Use ReaderWriterLockSlim for better concurrent read performance
private static readonly ReaderWriterLockSlim _rwLock = new(LockRecursionPolicy.SupportsRecursion);
private static readonly object _lockObject = new();
private static readonly Dictionary<string, string> _pageNameCache = [];
private static readonly Dictionary<string, (string HeaderNorm, string DescNorm)> _normalizedTextCache = new();
private static readonly Dictionary<string, Type> _pageTypeCache = new();
@@ -40,15 +39,10 @@ namespace Microsoft.PowerToys.Settings.UI.Services
{
get
{
_rwLock.EnterReadLock();
try
lock (_lockObject)
{
return _index;
}
finally
{
_rwLock.ExitReadLock();
}
}
}
@@ -56,32 +50,16 @@ namespace Microsoft.PowerToys.Settings.UI.Services
{
get
{
_rwLock.EnterReadLock();
try
lock (_lockObject)
{
return _isIndexBuilt;
}
finally
{
_rwLock.ExitReadLock();
}
}
}
/// <summary>
/// Builds the search index asynchronously on a background thread.
/// </summary>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A task representing the async operation.</returns>
public static Task BuildIndexAsync(CancellationToken cancellationToken = default)
{
return Task.Run(() => BuildIndex(), cancellationToken);
}
public static void BuildIndex()
{
_rwLock.EnterWriteLock();
try
lock (_lockObject)
{
if (_isIndexBuilt || _isIndexBuilding)
{
@@ -94,41 +72,27 @@ namespace Microsoft.PowerToys.Settings.UI.Services
_normalizedTextCache.Clear();
_pageTypeCache.Clear();
}
finally
{
_rwLock.ExitWriteLock();
}
try
{
var builder = ImmutableArray.CreateBuilder<SettingEntry>();
LoadIndexFromPrebuiltData(builder);
_rwLock.EnterWriteLock();
try
lock (_lockObject)
{
_index = builder.ToImmutable();
_isIndexBuilt = true;
_isIndexBuilding = false;
}
finally
{
_rwLock.ExitWriteLock();
}
}
catch (Exception ex)
{
Debug.WriteLine($"[SearchIndexService] CRITICAL ERROR building search index: {ex.Message}\n{ex.StackTrace}");
_rwLock.EnterWriteLock();
try
lock (_lockObject)
{
_isIndexBuilding = false;
_isIndexBuilt = false;
}
finally
{
_rwLock.ExitWriteLock();
}
}
}
@@ -305,39 +269,18 @@ namespace Microsoft.PowerToys.Settings.UI.Services
return null;
}
// Try read lock first for cache lookup
_rwLock.EnterReadLock();
try
lock (_lockObject)
{
if (_pageTypeCache.TryGetValue(pageTypeName, out var cached))
{
return cached;
}
}
finally
{
_rwLock.ExitReadLock();
}
// Cache miss - need write lock to add
_rwLock.EnterWriteLock();
try
{
// Double-check after acquiring write lock
if (_pageTypeCache.TryGetValue(pageTypeName, out var cached))
{
return cached;
}
var assembly = typeof(GeneralPage).Assembly;
var type = assembly.GetType($"Microsoft.PowerToys.Settings.UI.Views.{pageTypeName}");
_pageTypeCache[pageTypeName] = type;
return type;
}
finally
{
_rwLock.ExitWriteLock();
}
}
private static (string HeaderNorm, string DescNorm) GetNormalizedTexts(SettingEntry entry)
@@ -348,37 +291,19 @@ namespace Microsoft.PowerToys.Settings.UI.Services
}
var key = entry.ElementUid ?? $"{entry.PageTypeName}|{entry.ElementName}";
// Try read lock first for cache lookup
_rwLock.EnterReadLock();
try
lock (_lockObject)
{
if (_normalizedTextCache.TryGetValue(key, out var cached))
{
return cached;
}
}
finally
{
_rwLock.ExitReadLock();
}
// Cache miss - compute values and add to cache
var headerNorm = NormalizeString(entry.Header);
var descNorm = NormalizeString(entry.Description);
_rwLock.EnterWriteLock();
try
lock (_lockObject)
{
// Double-check after acquiring write lock
if (!_normalizedTextCache.ContainsKey(key))
{
_normalizedTextCache[key] = (headerNorm, descNorm);
}
}
finally
{
_rwLock.ExitWriteLock();
_normalizedTextCache[key] = (headerNorm, descNorm);
}
return (headerNorm, descNorm);

View File

@@ -1,125 +0,0 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
namespace Microsoft.PowerToys.Settings.UI.Services
{
/// <summary>
/// Implementation of ISettingsService that provides centralized settings management
/// with async loading and caching support.
/// </summary>
public class SettingsService : ISettingsService
{
private readonly SettingsUtils _settingsUtils;
private ISettingsRepository<GeneralSettings> _generalSettingsRepository;
private bool _isLoaded;
/// <summary>
/// Initializes a new instance of the <see cref="SettingsService"/> class.
/// </summary>
public SettingsService()
: this(SettingsUtils.Default)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="SettingsService"/> class.
/// </summary>
/// <param name="settingsUtils">The settings utilities instance.</param>
public SettingsService(SettingsUtils settingsUtils)
{
_settingsUtils = settingsUtils ?? throw new ArgumentNullException(nameof(settingsUtils));
}
/// <inheritdoc/>
public ISettingsRepository<GeneralSettings> GeneralSettingsRepository
{
get
{
EnsureLoaded();
return _generalSettingsRepository;
}
}
/// <inheritdoc/>
public GeneralSettings GeneralSettings
{
get
{
EnsureLoaded();
return _generalSettingsRepository?.SettingsConfig;
}
}
/// <inheritdoc/>
public SettingsUtils SettingsUtils => _settingsUtils;
/// <inheritdoc/>
public bool IsLoaded => _isLoaded;
/// <inheritdoc/>
public event Action<GeneralSettings> GeneralSettingsChanged;
/// <inheritdoc/>
public Task LoadAsync(CancellationToken cancellationToken = default)
{
return Task.Run(
() =>
{
cancellationToken.ThrowIfCancellationRequested();
// Load general settings repository
_generalSettingsRepository = SettingsRepository<GeneralSettings>.GetInstance(_settingsUtils);
_generalSettingsRepository.SettingsChanged += OnGeneralSettingsChanged;
_isLoaded = true;
},
cancellationToken);
}
/// <inheritdoc/>
public ISettingsRepository<T> GetRepository<T>()
where T : class, ISettingsConfig, new()
{
return SettingsRepository<T>.GetInstance(_settingsUtils);
}
/// <inheritdoc/>
public Task SaveAsync<T>(T settings, CancellationToken cancellationToken = default)
where T : class, ISettingsConfig, new()
{
return Task.Run(
() =>
{
cancellationToken.ThrowIfCancellationRequested();
var json = settings.ToJsonString();
_settingsUtils.SaveSettings(json, settings.GetModuleName());
},
cancellationToken);
}
private void EnsureLoaded()
{
if (!_isLoaded)
{
// Synchronously load if not already loaded
_generalSettingsRepository = SettingsRepository<GeneralSettings>.GetInstance(_settingsUtils);
_generalSettingsRepository.SettingsChanged += OnGeneralSettingsChanged;
_isLoaded = true;
}
}
private void OnGeneralSettingsChanged(GeneralSettings newSettings)
{
GeneralSettingsChanged?.Invoke(newSettings);
}
}
}

View File

@@ -1,84 +0,0 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Concurrent;
namespace Microsoft.PowerToys.Settings.UI.Services
{
/// <summary>
/// Provides lazy service resolution with optional caching.
/// Used by Views to obtain services without direct DI container access.
/// </summary>
public class ViewModelLocator
{
private readonly IServiceProvider _serviceProvider;
private readonly ConcurrentDictionary<Type, object> _cachedServices;
private readonly bool _enableCaching;
/// <summary>
/// Initializes a new instance of the <see cref="ViewModelLocator"/> class.
/// </summary>
/// <param name="serviceProvider">The DI service provider.</param>
/// <param name="enableCaching">Whether to cache service instances.</param>
public ViewModelLocator(IServiceProvider serviceProvider, bool enableCaching = false)
{
_serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
_enableCaching = enableCaching;
_cachedServices = enableCaching ? new ConcurrentDictionary<Type, object>() : null;
}
/// <summary>
/// Gets or creates a service of the specified type.
/// </summary>
/// <typeparam name="TService">The type of service to get.</typeparam>
/// <returns>The service instance.</returns>
public TService GetService<TService>()
where TService : class
{
return (TService)GetService(typeof(TService));
}
/// <summary>
/// Gets or creates a service of the specified type.
/// </summary>
/// <param name="serviceType">The type of service to get.</param>
/// <returns>The service instance.</returns>
public object GetService(Type serviceType)
{
if (_enableCaching && _cachedServices != null)
{
return _cachedServices.GetOrAdd(serviceType, type =>
_serviceProvider.GetService(type));
}
return _serviceProvider.GetService(serviceType);
}
/// <summary>
/// Clears cached services (if caching is enabled).
/// </summary>
public void ClearCache()
{
_cachedServices?.Clear();
}
/// <summary>
/// Removes a specific service from the cache (if caching is enabled).
/// </summary>
/// <typeparam name="TService">The type of service to remove.</typeparam>
public void RemoveFromCache<TService>()
where TService : class
{
_cachedServices?.TryRemove(typeof(TService), out _);
}
// Convenience properties for common services
/// <summary>
/// Gets the navigation service.
/// </summary>
public INavigationService NavigationService => GetService<INavigationService>();
}
}

View File

@@ -10,7 +10,6 @@ using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
using ManagedCommon;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.PowerToys.Settings.UI.Helpers;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Settings.UI.Library.Telemetry.Events;
@@ -76,14 +75,8 @@ namespace Microsoft.PowerToys.Settings.UI
/// </summary>
public App()
{
// Configure DI container first (before any other initialization)
ConfigureServices();
Logger.InitializeLogger(@"\Settings\Logs");
// Initialize logger on background thread to avoid blocking startup
Task.Run(() => Logger.InitializeLogger(@"\Settings\Logs"));
// Load language synchronously as it affects UI rendering
// but cache for future use
string appLanguage = LanguageHelper.LoadLanguage();
if (!string.IsNullOrEmpty(appLanguage))
{
@@ -94,16 +87,11 @@ namespace Microsoft.PowerToys.Settings.UI
UnhandledException += App_UnhandledException;
// Start event waiter on background thread to avoid blocking startup
Task.Run(() =>
NativeEventWaiter.WaitForEventLoop(
Constants.PowerToysRunnerTerminateSettingsEvent(), () =>
{
NativeEventWaiter.WaitForEventLoop(
Constants.PowerToysRunnerTerminateSettingsEvent(),
() =>
{
EtwTrace?.Dispose();
Environment.Exit(0);
});
EtwTrace?.Dispose();
Environment.Exit(0);
});
}
@@ -112,29 +100,6 @@ namespace Microsoft.PowerToys.Settings.UI
Logger.LogError("Unhandled exception", e.Exception);
}
/// <summary>
/// Configures the dependency injection container.
/// </summary>
private static void ConfigureServices()
{
AppServices.Configure(services =>
{
// Additional service registrations can be added here
// For now, the core services are registered in AppServices.ConfigureCoreServices
});
}
/// <summary>
/// Gets a service of type T from the DI container.
/// </summary>
/// <typeparam name="T">The type of service to get.</typeparam>
/// <returns>The service instance.</returns>
public static T GetService<T>()
where T : class
{
return AppServices.GetService<T>();
}
public static void OpenSettingsWindow(Type type = null, bool ensurePageIsSelected = false)
{
if (settingsWindow == null)

View File

@@ -63,7 +63,6 @@ namespace Microsoft.PowerToys.Settings.UI.Views
Loaded += async (s, e) =>
{
ViewModel.OnPageLoaded();
await ViewModel.InitializeAsync();
UpdatePasteAIUIVisibility();
await UpdateFoundryLocalUIAsync();
};

View File

@@ -17,9 +17,6 @@ namespace Microsoft.PowerToys.Settings.UI.Views
ViewModel = new CmdNotFoundViewModel();
DataContext = ViewModel;
InitializeComponent();
// Defer heavy PowerShell script execution to async initialization
this.Loaded += async (s, e) => await ViewModel.InitializeAsync();
}
}
}

View File

@@ -26,12 +26,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views
ShellPage.SendDefaultIPCMessage,
DispatcherQueue);
DataContext = ViewModel;
Loaded += async (s, e) =>
{
ViewModel.OnPageLoaded();
await ViewModel.InitializeAsync();
};
Loaded += (s, e) => ViewModel.OnPageLoaded();
InitializeComponent();
}

View File

@@ -19,13 +19,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views
var settingsUtils = SettingsUtils.Default;
ViewModel = new FancyZonesViewModel(settingsUtils, SettingsRepository<GeneralSettings>.GetInstance(settingsUtils), SettingsRepository<FancyZonesSettings>.GetInstance(settingsUtils), ShellPage.SendDefaultIPCMessage);
DataContext = ViewModel;
// Defer heavy property loading to async initialization
Loaded += async (s, e) =>
{
ViewModel.OnPageLoaded();
await ViewModel.InitializeAsync();
};
Loaded += (s, e) => ViewModel.OnPageLoaded();
}
private void OpenColorsSettings_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)

View File

@@ -19,9 +19,6 @@ namespace Microsoft.PowerToys.Settings.UI.Views
ViewModel = new FileLocksmithViewModel(settingsUtils, SettingsRepository<GeneralSettings>.GetInstance(settingsUtils), ShellPage.SendDefaultIPCMessage);
DataContext = ViewModel;
InitializeComponent();
// Defer heavy file I/O to async initialization
this.Loaded += async (s, e) => await ViewModel.InitializeAsync();
}
public void RefreshEnabledState()

View File

@@ -94,11 +94,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views
doRefreshBackupRestoreStatus(100);
this.Loaded += async (s, e) =>
{
ViewModel.OnPageLoaded();
await ViewModel.InitializeAsync();
};
this.Loaded += (s, e) => ViewModel.OnPageLoaded();
}
private void OpenColorsSettings_Click(object sender, RoutedEventArgs e)

View File

@@ -27,9 +27,6 @@ namespace Microsoft.PowerToys.Settings.UI.Views
ViewModel = new ImageResizerViewModel(settingsUtils, SettingsRepository<GeneralSettings>.GetInstance(settingsUtils), ShellPage.SendDefaultIPCMessage, loader);
DataContext = ViewModel;
// Defer heavy file I/O to async initialization
this.Loaded += async (s, e) => await ViewModel.InitializeAsync();
}
public async void DeleteCustomSize(object sender, RoutedEventArgs e)

View File

@@ -38,9 +38,6 @@ namespace Microsoft.PowerToys.Settings.UI.Views
InitializeComponent();
DataContext = ViewModel;
// Defer heavy file I/O to async initialization
this.Loaded += async (s, e) => await ViewModel.InitializeAsync();
}
private void OnConfigFileUpdate()

View File

@@ -23,13 +23,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views
DispatcherQueue);
DataContext = ViewModel;
InitializeComponent();
// Defer heavy settings loading and file watcher setup
Loaded += async (s, e) =>
{
ViewModel.OnPageLoaded();
await ViewModel.InitializeAsync();
};
Loaded += (s, e) => ViewModel.OnPageLoaded();
}
public void RefreshEnabledState()

View File

@@ -21,13 +21,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views
ViewModel = new PowerAccentViewModel(settingsUtils, SettingsRepository<GeneralSettings>.GetInstance(settingsUtils), ShellPage.SendDefaultIPCMessage);
DataContext = ViewModel;
this.InitializeComponent();
// Defer heavy settings loading and language initialization
this.Loaded += async (s, e) =>
{
await ViewModel.InitializeAsync();
this.InitializeControlsStates();
};
this.InitializeControlsStates();
}
public void RefreshEnabledState()

View File

@@ -20,9 +20,6 @@ namespace Microsoft.PowerToys.Settings.UI.Views
ViewModel = new PowerRenameViewModel(settingsUtils, SettingsRepository<GeneralSettings>.GetInstance(settingsUtils), ShellPage.SendDefaultIPCMessage);
DataContext = ViewModel;
// Defer heavy settings I/O to async initialization
this.Loaded += async (s, e) => await ViewModel.InitializeAsync();
}
public void RefreshEnabledState()

View File

@@ -135,11 +135,6 @@ namespace Microsoft.PowerToys.Settings.UI.Views
ViewModel = new ShellViewModel(SettingsRepository<GeneralSettings>.GetInstance(settingsUtils));
DataContext = ViewModel;
ShellHandler = this;
// Enable page caching for better navigation performance
// This allows frequently visited pages to be cached instead of recreated
shellFrame.CacheSize = 10;
ViewModel.Initialize(shellFrame, navigationView, KeyboardAccelerators);
// NL moved navigation to general page to the moment when the window is first activated (to not make flyout window disappear)
@@ -390,8 +385,11 @@ namespace Microsoft.PowerToys.Settings.UI.Views
private void ShellPage_Loaded(object sender, RoutedEventArgs e)
{
// Build search index asynchronously on background thread
_ = SearchIndexService.BuildIndexAsync();
Task.Run(() =>
{
SearchIndexService.BuildIndex();
})
.ContinueWith(_ => { });
}
private void NavigationView_DisplayModeChanged(NavigationView sender, NavigationViewDisplayModeChangedEventArgs args)

View File

@@ -12,8 +12,6 @@ using System.IO.Abstractions;
using System.Linq;
using System.Runtime.Versioning;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using global::PowerToys.GPOWrapper;
using Microsoft.PowerToys.Settings.UI.Helpers;
@@ -78,60 +76,52 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
GeneralSettingsConfig = settingsRepository.SettingsConfig;
// To obtain the settings configurations of Fancy zones.
ArgumentNullException.ThrowIfNull(settingsRepository);
// To obtain the settings configurations of Advanced Paste.
ArgumentNullException.ThrowIfNull(advancedPasteSettingsRepository);
_dispatcherQueue = DispatcherQueue.GetForCurrentThread();
_settingsUtils = settingsUtils ?? throw new ArgumentNullException(nameof(settingsUtils));
ArgumentNullException.ThrowIfNull(advancedPasteSettingsRepository);
_advancedPasteSettings = advancedPasteSettingsRepository.SettingsConfig ?? throw new ArgumentException("SettingsConfig cannot be null", nameof(advancedPasteSettingsRepository));
_advancedPasteSettings = advancedPasteSettingsRepository.SettingsConfig;
if (_advancedPasteSettings.Properties is null)
{
throw new ArgumentException("AdvancedPasteSettings.Properties cannot be null", nameof(advancedPasteSettingsRepository));
}
// Ensure AdditionalActions and CustomActions are initialized to prevent null reference exceptions
// This handles legacy settings files that may be missing these properties
_advancedPasteSettings.Properties.AdditionalActions ??= new AdvancedPasteAdditionalActions();
_advancedPasteSettings.Properties.CustomActions ??= new AdvancedPasteCustomActions();
AttachConfigurationHandlers();
// set the callback functions value to handle outgoing IPC message.
SendConfigMSG = ipcMSGCallBackFunc;
_additionalActions = _advancedPasteSettings.Properties.AdditionalActions;
_customActions = _advancedPasteSettings.Properties.CustomActions.Value;
_customActions = _advancedPasteSettings.Properties.CustomActions.Value ?? new ObservableCollection<AdvancedPasteCustomAction>();
SetupSettingsFileWatcher();
InitializePasteAIProviderState();
InitializeEnabledValue();
MigrateLegacyAIEnablement();
// Defer heavy initialization to InitializeCoreAsync
}
foreach (var action in _additionalActions.GetAllActions())
{
action.PropertyChanged += OnAdditionalActionPropertyChanged;
}
/// <summary>
/// Performs deferred initialization - sets up file watcher, AI provider, handlers.
/// </summary>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A task representing the async operation.</returns>
protected override Task InitializeCoreAsync(CancellationToken cancellationToken = default)
{
return Task.Run(
() =>
{
AttachConfigurationHandlers();
SetupSettingsFileWatcher();
InitializePasteAIProviderState();
MigrateLegacyAIEnablement();
foreach (var customAction in _customActions)
{
customAction.PropertyChanged += OnCustomActionPropertyChanged;
}
foreach (var action in _additionalActions.GetAllActions())
{
action.PropertyChanged += OnAdditionalActionPropertyChanged;
}
foreach (var customAction in _customActions)
{
customAction.PropertyChanged += OnCustomActionPropertyChanged;
}
_dispatcherQueue.TryEnqueue(() =>
{
_customActions.CollectionChanged += OnCustomActionsCollectionChanged;
UpdateCustomActionsCanMoveUpDown();
});
},
cancellationToken);
_customActions.CollectionChanged += OnCustomActionsCollectionChanged;
UpdateCustomActionsCanMoveUpDown();
}
public override Dictionary<string, HotkeySettings[]> GetAllHotkeySettings()
@@ -487,7 +477,13 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
public PasteAIConfiguration PasteAIConfiguration
{
get => _advancedPasteSettings.Properties.PasteAIConfiguration;
get
{
// Ensure PasteAIConfiguration is never null for XAML binding
_advancedPasteSettings.Properties.PasteAIConfiguration ??= new PasteAIConfiguration();
return _advancedPasteSettings.Properties.PasteAIConfiguration;
}
set
{
if (!ReferenceEquals(value, _advancedPasteSettings.Properties.PasteAIConfiguration))

View File

@@ -9,8 +9,6 @@ using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using global::PowerToys.GPOWrapper;
using ManagedCommon;
@@ -21,7 +19,7 @@ using Microsoft.PowerToys.Telemetry;
namespace Microsoft.PowerToys.Settings.UI.ViewModels
{
public partial class CmdNotFoundViewModel : Observable, IAsyncInitializable
public partial class CmdNotFoundViewModel : Observable
{
public ButtonClickCommand CheckRequirementsEventHandler => new ButtonClickCommand(CheckCommandNotFoundRequirements);
@@ -47,7 +45,11 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
public CmdNotFoundViewModel()
{
// Lightweight GPO initialization only
InitializeEnabledValue();
}
private void InitializeEnabledValue()
{
_enabledGpoRuleConfiguration = GPOWrapper.GetConfiguredCmdNotFoundEnabledValue();
_moduleIsGpoEnabled = _enabledGpoRuleConfiguration == GpoRuleConfigured.Enabled;
_moduleIsGpoDisabled = _enabledGpoRuleConfiguration == GpoRuleConfigured.Disabled;
@@ -55,45 +57,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
// Update PATH environment variable to get pwsh.exe on further calls.
Environment.SetEnvironmentVariable("PATH", (Environment.GetEnvironmentVariable("PATH", EnvironmentVariableTarget.Machine) ?? string.Empty) + ";" + (Environment.GetEnvironmentVariable("PATH", EnvironmentVariableTarget.User) ?? string.Empty), EnvironmentVariableTarget.Process);
// CheckCommandNotFoundRequirements() will be called when user navigates to page or clicks button
}
/// <summary>
/// Gets a value indicating whether the ViewModel has been initialized.
/// </summary>
public bool IsInitialized { get; private set; }
/// <summary>
/// Gets a value indicating whether initialization is in progress.
/// </summary>
public bool IsLoading { get; private set; }
/// <summary>
/// Initializes the ViewModel asynchronously - runs PowerShell checks.
/// Note: This must run on UI thread as it updates bound properties.
/// </summary>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A task representing the async operation.</returns>
public Task InitializeAsync(CancellationToken cancellationToken = default)
{
if (IsInitialized)
{
return Task.CompletedTask;
}
IsLoading = true;
OnPropertyChanged(nameof(IsLoading));
// Run PowerShell checks synchronously on UI thread since they update many bound properties
// This preserves original behavior - the UI shows loading state while checks run
CheckCommandNotFoundRequirements();
IsLoading = false;
IsInitialized = true;
OnPropertyChanged(nameof(IsLoading));
OnPropertyChanged(nameof(IsInitialized));
return Task.CompletedTask;
}
private string _commandOutputLog;

View File

@@ -10,9 +10,6 @@ using System.Linq;
using System.Reflection;
using System.Text.Json;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using global::PowerToys.GPOWrapper;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Helpers;
@@ -41,8 +38,6 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
private Func<string, int> SendConfigMSG { get; }
private string _settingsPath;
public CmdPalViewModel(SettingsUtils settingsUtils, ISettingsRepository<GeneralSettings> settingsRepository, Func<string, int> ipcMSGCallBackFunc, DispatcherQueue uiDispatcherQueue)
{
ArgumentNullException.ThrowIfNull(settingsUtils);
@@ -60,44 +55,28 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
var localAppDataDir = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
#if DEBUG
_settingsPath = Path.Combine(localAppDataDir, "Packages", "Microsoft.CommandPalette.Dev_8wekyb3d8bbwe", "LocalState", "settings.json");
var settingsPath = Path.Combine(localAppDataDir, "Packages", "Microsoft.CommandPalette.Dev_8wekyb3d8bbwe", "LocalState", "settings.json");
#else
_settingsPath = Path.Combine(localAppDataDir, "Packages", "Microsoft.CommandPalette_8wekyb3d8bbwe", "LocalState", "settings.json");
var settingsPath = Path.Combine(localAppDataDir, "Packages", "Microsoft.CommandPalette_8wekyb3d8bbwe", "LocalState", "settings.json");
#endif
_hotkey = _cmdPalProperties.Hotkey;
// Defer file watcher creation to async initialization
_watcher = Helper.GetFileWatcher(settingsPath, () =>
{
_cmdPalProperties.InitializeHotkey();
_hotkey = _cmdPalProperties.Hotkey;
_uiDispatcherQueue.TryEnqueue(() =>
{
OnPropertyChanged(nameof(Hotkey));
});
});
// set the callback functions value to handle outgoing IPC message.
SendConfigMSG = ipcMSGCallBackFunc;
}
/// <summary>
/// Performs deferred initialization - creates file watcher on background thread.
/// </summary>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A task representing the async operation.</returns>
protected override Task InitializeCoreAsync(CancellationToken cancellationToken = default)
{
return Task.Run(
() =>
{
_watcher = Helper.GetFileWatcher(
_settingsPath,
() =>
{
_cmdPalProperties.InitializeHotkey();
_hotkey = _cmdPalProperties.Hotkey;
_uiDispatcherQueue.TryEnqueue(() =>
{
OnPropertyChanged(nameof(Hotkey));
});
});
},
cancellationToken);
}
private void InitializeEnabledValue()
{
_enabledGpoRuleConfiguration = GPOWrapper.GetConfiguredCmdPalEnabledValue();

View File

@@ -7,7 +7,6 @@ using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO.Abstractions;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Threading;
using CommunityToolkit.WinUI.Controls;
@@ -131,36 +130,6 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
RefreshShortcutModules();
}
/// <summary>
/// Asynchronously initializes the Dashboard ViewModel.
/// This method performs heavy initialization work on a background thread.
/// </summary>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A task representing the async operation.</returns>
protected override async Task InitializeCoreAsync(CancellationToken cancellationToken = default)
{
// If already initialized synchronously in constructor, skip
if (AllModules.Count > 0)
{
return;
}
await Task.Run(
() =>
{
cancellationToken.ThrowIfCancellationRequested();
BuildModuleList();
},
cancellationToken);
// UI updates must happen on dispatcher thread
dispatcher.Invoke(() =>
{
SortModuleList();
RefreshShortcutModules();
});
}
private void OnSettingsChanged(GeneralSettings newSettings)
{
dispatcher.BeginInvoke(() =>

View File

@@ -5,9 +5,6 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using global::PowerToys.GPOWrapper;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Helpers;
@@ -68,31 +65,6 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
LaunchEditorEventHandler = new ButtonClickCommand(LaunchEditor);
// set the callback functions value to handle outgoing IPC message.
SendConfigMSG = ipcMSGCallBackFunc;
InitializeEnabledValue();
// Defer heavy property initialization to InitializeCoreAsync
}
/// <summary>
/// Performs deferred initialization - loads all settings properties.
/// </summary>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A task representing the async operation.</returns>
protected override Task InitializeCoreAsync(CancellationToken cancellationToken = default)
{
return Task.Run(
() =>
{
LoadSettingsProperties();
},
cancellationToken);
}
private void LoadSettingsProperties()
{
_shiftDrag = Settings.Properties.FancyzonesShiftDrag.Value;
_mouseSwitch = Settings.Properties.FancyzonesMouseSwitch.Value;
_mouseMiddleButtonSpanningMultipleZones = Settings.Properties.FancyzonesMouseMiddleClickSpanningMultipleZones.Value;
@@ -124,6 +96,9 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
NextTabHotkey = Settings.Properties.FancyzonesNextTabHotkey.Value;
PrevTabHotkey = Settings.Properties.FancyzonesPrevTabHotkey.Value;
// set the callback functions value to handle outgoing IPC message.
SendConfigMSG = ipcMSGCallBackFunc;
string inactiveColor = Settings.Properties.FancyzonesInActiveColor.Value;
_zoneInActiveColor = !string.IsNullOrEmpty(inactiveColor) ? inactiveColor : ConfigDefaults.DefaultFancyZonesInActiveColor;
@@ -136,6 +111,8 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
string numberColor = Settings.Properties.FancyzonesNumberColor.Value;
_zoneNumberColor = !string.IsNullOrEmpty(numberColor) ? numberColor : ConfigDefaults.DefaultFancyzonesNumberColor;
InitializeEnabledValue();
_windows11 = OSVersionHelper.IsWindows11();
// Disable setting on windows 10
@@ -143,8 +120,6 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
{
DisableRoundCornersOnWindowSnap = false;
}
// Note: OnPropertyChanged calls removed - runs on background thread
}
private void InitializeEnabledValue()

View File

@@ -5,8 +5,6 @@
using System;
using System.Globalization;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using global::PowerToys.GPOWrapper;
using Microsoft.PowerToys.Settings.UI.Library;
@@ -16,7 +14,7 @@ using Microsoft.PowerToys.Settings.UI.SerializationContext;
namespace Microsoft.PowerToys.Settings.UI.ViewModels
{
public partial class FileLocksmithViewModel : Observable, IAsyncInitializable
public partial class FileLocksmithViewModel : Observable
{
private GeneralSettings GeneralSettingsConfig { get; set; }
@@ -36,65 +34,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
ArgumentNullException.ThrowIfNull(settingsRepository);
GeneralSettingsConfig = settingsRepository.SettingsConfig;
_settingsConfigFileFolder = configFileSubfolder;
// Initialize with defaults - actual settings loaded in InitializeAsync
Settings = new FileLocksmithSettings(new FileLocksmithLocalProperties());
InitializeEnabledValue();
// set the callback functions value to handle outgoing IPC message.
SendConfigMSG = ipcMSGCallBackFunc;
_fileLocksmithEnabledOnContextExtendedMenu = Settings.Properties.ExtendedContextMenuOnly.Value;
}
/// <summary>
/// Gets a value indicating whether the ViewModel has been initialized.
/// </summary>
public bool IsInitialized { get; private set; }
/// <summary>
/// Gets a value indicating whether initialization is in progress.
/// </summary>
public bool IsLoading { get; private set; }
/// <summary>
/// Initializes the ViewModel asynchronously, loading settings from disk.
/// </summary>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A task representing the async operation.</returns>
public async Task InitializeAsync(CancellationToken cancellationToken = default)
{
if (IsInitialized)
{
return;
}
IsLoading = true;
OnPropertyChanged(nameof(IsLoading));
try
{
await Task.Run(
() =>
{
LoadSettingsFromDisk();
},
cancellationToken);
IsInitialized = true;
}
finally
{
IsLoading = false;
OnPropertyChanged(nameof(IsLoading));
OnPropertyChanged(nameof(IsInitialized));
}
}
private void LoadSettingsFromDisk()
{
try
{
FileLocksmithLocalProperties localSettings = _settingsUtils.GetSettingsOrDefault<FileLocksmithLocalProperties>(GetSettingsSubPath(), "file-locksmith-settings.json");
@@ -107,6 +47,11 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
_settingsUtils.SaveSettings(localSettings.ToJsonString(), GetSettingsSubPath(), "file-locksmith-settings.json");
}
InitializeEnabledValue();
// set the callback functions value to handle outgoing IPC message.
SendConfigMSG = ipcMSGCallBackFunc;
_fileLocksmithEnabledOnContextExtendedMenu = Settings.Properties.ExtendedContextMenuOnly.Value;
}

View File

@@ -14,7 +14,6 @@ using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text.Json;
using System.Text.Json.Serialization.Metadata;
using System.Threading;
using System.Threading.Tasks;
using global::PowerToys.GPOWrapper;
@@ -214,31 +213,14 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
_fileWatcher = Helper.GetFileWatcher(string.Empty, UpdatingSettings.SettingsFile, dispatcherAction);
}
// Defer heavy I/O operations to async initialization
// Language initialization moved to InitializeCoreAsync for faster startup
}
// Diagnostic data retention policy
string etwDirPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Microsoft\\PowerToys\\etw");
DeleteDiagnosticDataOlderThan28Days(etwDirPath);
/// <summary>
/// Performs deferred initialization tasks that don't need to block the UI.
/// </summary>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A task representing the async operation.</returns>
protected override Task InitializeCoreAsync(CancellationToken cancellationToken = default)
{
return Task.Run(
() =>
{
// Diagnostic data retention policy - defer to background
string etwDirPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Microsoft\\PowerToys\\etw");
DeleteDiagnosticDataOlderThan28Days(etwDirPath);
string localLowEtwDirPath = Path.Combine(Environment.GetEnvironmentVariable("USERPROFILE"), "AppData", "LocalLow", "Microsoft", "PowerToys", "etw");
DeleteDiagnosticDataOlderThan28Days(localLowEtwDirPath);
string localLowEtwDirPath = Path.Combine(Environment.GetEnvironmentVariable("USERPROFILE"), "AppData", "LocalLow", "Microsoft", "PowerToys", "etw");
DeleteDiagnosticDataOlderThan28Days(localLowEtwDirPath);
// Initialize languages on background thread
InitializeLanguages();
},
cancellationToken);
InitializeLanguages();
}
// Supported languages. Taken from Resources.wxs + default + en-US

View File

@@ -1,34 +0,0 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.PowerToys.Settings.UI.ViewModels
{
/// <summary>
/// Interface for ViewModels that require async initialization after construction.
/// This enables separating heavy loading logic from constructors to improve page navigation performance.
/// </summary>
public interface IAsyncInitializable
{
/// <summary>
/// Gets a value indicating whether the ViewModel is currently loading.
/// </summary>
bool IsLoading { get; }
/// <summary>
/// Gets a value indicating whether the ViewModel has been initialized.
/// </summary>
bool IsInitialized { get; }
/// <summary>
/// Initializes the ViewModel asynchronously. This method should be called
/// after navigation to the page, not in the constructor.
/// </summary>
/// <param name="cancellationToken">Cancellation token to cancel initialization.</param>
/// <returns>A task representing the async operation.</returns>
Task InitializeAsync(CancellationToken cancellationToken = default);
}
}

View File

@@ -10,9 +10,6 @@ using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using global::PowerToys.GPOWrapper;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Library;
@@ -21,7 +18,7 @@ using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
namespace Microsoft.PowerToys.Settings.UI.ViewModels;
public partial class ImageResizerViewModel : Observable, IAsyncInitializable
public partial class ImageResizerViewModel : Observable
{
private static readonly string DefaultPresetNamePrefix =
Helpers.ResourceLoaderInstance.ResourceLoader.GetString("ImageResizer_DefaultSize_NewSizePrefix");
@@ -39,12 +36,12 @@ public partial class ImageResizerViewModel : Observable, IAsyncInitializable
/// <summary>
/// Used to skip saving settings to file during initialization.
/// </summary>
private bool _isInitializing;
private readonly bool _isInitializing;
/// <summary>
/// Holds defaults for new presets.
/// </summary>
private ImageSize _customSize;
private readonly ImageSize _customSize;
private GeneralSettings GeneralSettingsConfig { get; set; }
@@ -56,81 +53,17 @@ public partial class ImageResizerViewModel : Observable, IAsyncInitializable
private Func<string, int> SendConfigMSG { get; }
private Func<string, string> _resourceLoader;
public ImageResizerViewModel(SettingsUtils settingsUtils, ISettingsRepository<GeneralSettings> settingsRepository, Func<string, int> ipcMSGCallBackFunc, Func<string, string> resourceLoader)
{
_isInitializing = true;
_settingsUtils = settingsUtils ?? throw new ArgumentNullException(nameof(settingsUtils));
_resourceLoader = resourceLoader;
// To obtain the general settings configurations of PowerToys.
ArgumentNullException.ThrowIfNull(settingsRepository);
GeneralSettingsConfig = settingsRepository.SettingsConfig;
// set the callback functions value to handle outgoing IPC message.
SendConfigMSG = ipcMSGCallBackFunc;
InitializeEnabledValue();
// Initialize with defaults - actual settings loaded in InitializeAsync
Settings = new ImageResizerSettings(resourceLoader);
Sizes = new ObservableCollection<ImageSize>();
_customSize = Settings.Properties.ImageresizerCustomSize.Value;
_isInitializing = false;
}
/// <summary>
/// Gets a value indicating whether the ViewModel has been initialized.
/// </summary>
public bool IsInitialized { get; private set; }
/// <summary>
/// Gets a value indicating whether initialization is in progress.
/// </summary>
public bool IsLoading { get; private set; }
/// <summary>
/// Initializes the ViewModel asynchronously, loading settings from disk.
/// </summary>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A task representing the async operation.</returns>
public async Task InitializeAsync(CancellationToken cancellationToken = default)
{
if (IsInitialized)
{
return;
}
IsLoading = true;
OnPropertyChanged(nameof(IsLoading));
try
{
await Task.Run(
() =>
{
LoadSettingsFromDisk();
},
cancellationToken);
IsInitialized = true;
}
finally
{
IsLoading = false;
OnPropertyChanged(nameof(IsLoading));
OnPropertyChanged(nameof(IsInitialized));
}
}
private void LoadSettingsFromDisk()
{
_isInitializing = true;
try
{
Settings = _settingsUtils.GetSettings<ImageResizerSettings>(ModuleName);
@@ -145,10 +78,15 @@ public partial class ImageResizerViewModel : Observable, IAsyncInitializable
throw;
}
#endif
Settings = new ImageResizerSettings(_resourceLoader);
Settings = new ImageResizerSettings(resourceLoader);
_settingsUtils.SaveSettings(Settings.ToJsonString(), ModuleName);
}
// set the callback functions value to handle outgoing IPC message.
SendConfigMSG = ipcMSGCallBackFunc;
InitializeEnabledValue();
Sizes = new ObservableCollection<ImageSize>(Settings.Properties.ImageresizerSizes.Value);
JPEGQualityLevel = Settings.Properties.ImageresizerJpegQualityLevel.Value;
PngInterlaceOption = Settings.Properties.ImageresizerPngInterlaceOption.Value;
@@ -157,10 +95,9 @@ public partial class ImageResizerViewModel : Observable, IAsyncInitializable
KeepDateModified = Settings.Properties.ImageresizerKeepDateModified.Value;
Encoder = GetEncoderIndex(Settings.Properties.ImageresizerFallbackEncoder.Value);
_isInitializing = false;
_customSize = Settings.Properties.ImageresizerCustomSize.Value;
// Note: OnPropertyChanged calls removed - properties updated directly
// and UI will refresh when page is navigated to
_isInitializing = false;
}
private void InitializeEnabledValue()

View File

@@ -23,7 +23,7 @@ using Microsoft.Win32;
namespace Microsoft.PowerToys.Settings.UI.ViewModels
{
public partial class KeyboardManagerViewModel : Observable, IAsyncInitializable
public partial class KeyboardManagerViewModel : Observable
{
private GeneralSettings GeneralSettingsConfig { get; set; }
@@ -74,57 +74,6 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
_settingsUtils = settingsUtils ?? throw new ArgumentNullException(nameof(settingsUtils));
// Defer heavy file I/O to InitializeAsync - just set defaults here
Settings = new KeyboardManagerSettings();
_profile = new KeyboardManagerProfile();
}
/// <summary>
/// Gets a value indicating whether the ViewModel has been initialized.
/// </summary>
public bool IsInitialized { get; private set; }
/// <summary>
/// Gets a value indicating whether initialization is in progress.
/// </summary>
public bool IsLoading { get; private set; }
/// <summary>
/// Initializes the ViewModel asynchronously, loading settings from disk.
/// </summary>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A task representing the async operation.</returns>
public async Task InitializeAsync(CancellationToken cancellationToken = default)
{
if (IsInitialized)
{
return;
}
IsLoading = true;
OnPropertyChanged(nameof(IsLoading));
try
{
await Task.Run(
() =>
{
LoadSettingsFromDisk();
},
cancellationToken);
IsInitialized = true;
}
finally
{
IsLoading = false;
OnPropertyChanged(nameof(IsLoading));
OnPropertyChanged(nameof(IsInitialized));
}
}
private void LoadSettingsFromDisk()
{
if (_settingsUtils.SettingsExists(PowerToyName))
{
try
@@ -150,6 +99,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
}
else
{
Settings = new KeyboardManagerSettings();
_settingsUtils.SaveSettings(Settings.ToJsonString(), PowerToyName);
}
}

View File

@@ -6,7 +6,6 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.PowerToys.Settings.UI.Helpers;
using Microsoft.PowerToys.Settings.UI.Library;
@@ -16,34 +15,14 @@ using Microsoft.PowerToys.Settings.UI.Services;
namespace Microsoft.PowerToys.Settings.UI.ViewModels
{
public abstract class PageViewModelBase : Observable, IAsyncInitializable, IDisposable
public abstract class PageViewModelBase : Observable, IDisposable
{
private readonly Dictionary<string, bool> _hotkeyConflictStatus = new Dictionary<string, bool>();
private readonly Dictionary<string, string> _hotkeyConflictTooltips = new Dictionary<string, string>();
private bool _disposed;
private bool _isLoading;
private bool _isInitialized;
protected abstract string ModuleName { get; }
/// <summary>
/// Gets or sets a value indicating whether the ViewModel is currently loading data.
/// </summary>
public bool IsLoading
{
get => _isLoading;
protected set => Set(ref _isLoading, value);
}
/// <summary>
/// Gets a value indicating whether the ViewModel has been initialized.
/// </summary>
public bool IsInitialized
{
get => _isInitialized;
private set => Set(ref _isInitialized, value);
}
protected PageViewModelBase()
{
if (GlobalHotkeyConflictManager.Instance != null)
@@ -52,43 +31,6 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
}
}
/// <summary>
/// Initializes the ViewModel asynchronously. Override this method in derived classes
/// to perform async initialization (e.g., loading settings from disk).
/// </summary>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A task representing the async operation.</returns>
public virtual async Task InitializeAsync(CancellationToken cancellationToken = default)
{
if (IsInitialized)
{
return;
}
IsLoading = true;
try
{
await InitializeCoreAsync(cancellationToken).ConfigureAwait(false);
IsInitialized = true;
}
finally
{
IsLoading = false;
}
}
/// <summary>
/// Override this method in derived classes to perform the actual async initialization.
/// This is called by <see cref="InitializeAsync"/> and is wrapped with loading state management.
/// </summary>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A task representing the async operation.</returns>
protected virtual Task InitializeCoreAsync(CancellationToken cancellationToken = default)
{
// Default implementation does nothing - derived classes override this
return Task.CompletedTask;
}
public virtual void OnPageLoaded()
{
Debug.WriteLine($"=== PAGE LOADED: {ModuleName} ===");

View File

@@ -8,9 +8,6 @@ using System.Globalization;
using System.IO;
using System.IO.Abstractions;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using global::PowerToys.GPOWrapper;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Helpers;
@@ -39,7 +36,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
private readonly DispatcherQueue _dispatcherQueue;
private readonly SettingsUtils _settingsUtils;
private PeekPreviewSettings _peekPreviewSettings;
private readonly PeekPreviewSettings _peekPreviewSettings;
private PeekSettings _peekSettings;
private GpoRuleConfigured _enabledGpoRuleConfiguration;
@@ -64,44 +61,17 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
_settingsUtils = settingsUtils ?? throw new ArgumentNullException(nameof(settingsUtils));
// Initialize with defaults - heavy I/O deferred to InitializeCoreAsync
_peekSettings = new PeekSettings();
_peekPreviewSettings = new PeekPreviewSettings();
// Load the application-specific settings, including preview items.
_peekSettings = _settingsUtils.GetSettingsOrDefault<PeekSettings>(PeekSettings.ModuleName);
_peekPreviewSettings = _settingsUtils.GetSettingsOrDefault<PeekPreviewSettings>(PeekSettings.ModuleName, PeekPreviewSettings.FileName);
SetupSettingsFileWatcher();
InitializeEnabledValue();
SendConfigMSG = ipcMSGCallBackFunc;
}
/// <summary>
/// Performs deferred initialization - loads settings and sets up file watcher.
/// </summary>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A task representing the async operation.</returns>
protected override Task InitializeCoreAsync(CancellationToken cancellationToken = default)
{
return Task.Run(
() =>
{
// Load settings from disk
_peekSettings = _settingsUtils.GetSettingsOrDefault<PeekSettings>(PeekSettings.ModuleName);
_peekPreviewSettings = _settingsUtils.GetSettingsOrDefault<PeekPreviewSettings>(PeekSettings.ModuleName, PeekPreviewSettings.FileName);
// Set up file watcher
SetupSettingsFileWatcher();
// Notify UI of property changes
_dispatcherQueue.TryEnqueue(() =>
{
OnPropertyChanged(nameof(ActivationShortcut));
OnPropertyChanged(nameof(AlwaysRunNotElevated));
OnPropertyChanged(nameof(CloseAfterLosingFocus));
OnPropertyChanged(nameof(ConfirmFileDelete));
});
},
cancellationToken);
}
/// <summary>
/// Set up the file watcher for the settings file. Used to respond to updates to the
/// ConfirmFileDelete setting by the user within the Peek application itself.

View File

@@ -6,9 +6,6 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using global::PowerToys.GPOWrapper;
using Microsoft.PowerToys.Settings.UI.Helpers;
using Microsoft.PowerToys.Settings.UI.Library;
@@ -18,13 +15,13 @@ using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
namespace Microsoft.PowerToys.Settings.UI.ViewModels
{
public partial class PowerAccentViewModel : Observable, IAsyncInitializable
public partial class PowerAccentViewModel : Observable
{
private readonly SettingsUtils _settingsUtils;
private GeneralSettings GeneralSettingsConfig { get; set; }
private PowerAccentSettings _powerAccentSettings;
private readonly PowerAccentSettings _powerAccentSettings;
private readonly SettingsUtils _settingsUtils;
private const string SpecialGroup = "QuickAccent_Group_Special";
private const string LanguageGroup = "QuickAccent_Group_Language";
@@ -101,66 +98,8 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
GeneralSettingsConfig = settingsRepository.SettingsConfig;
InitializeEnabledValue();
// Initialize with defaults - heavy work deferred to InitializeAsync
_powerAccentSettings = new PowerAccentSettings();
SelectedLanguageOptions = Array.Empty<PowerAccentLanguageModel>();
LanguageGroups = Array.Empty<PowerAccentLanguageGroupModel>();
// set the callback functions value to handle outgoing IPC message.
SendConfigMSG = ipcMSGCallBackFunc;
}
/// <summary>
/// Gets a value indicating whether the ViewModel has been initialized.
/// </summary>
public bool IsInitialized { get; private set; }
/// <summary>
/// Gets a value indicating whether initialization is in progress.
/// </summary>
public bool IsLoading { get; private set; }
/// <summary>
/// Initializes the ViewModel asynchronously, loading settings and languages.
/// </summary>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A task representing the async operation.</returns>
public async Task InitializeAsync(CancellationToken cancellationToken = default)
{
if (IsInitialized)
{
return;
}
IsLoading = true;
OnPropertyChanged(nameof(IsLoading));
try
{
await Task.Run(
() =>
{
LoadSettingsAndLanguages();
},
cancellationToken);
IsInitialized = true;
}
finally
{
IsLoading = false;
OnPropertyChanged(nameof(IsLoading));
OnPropertyChanged(nameof(IsInitialized));
}
}
private void LoadSettingsAndLanguages()
{
// Initialize languages (resource loading + sorting)
InitializeLanguages();
// Load settings from disk
if (_settingsUtils.SettingsExists(PowerAccentSettings.ModuleName))
{
_powerAccentSettings = _settingsUtils.GetSettingsOrDefault<PowerAccentSettings>(PowerAccentSettings.ModuleName);
@@ -171,13 +110,14 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
}
_inputTimeMs = _powerAccentSettings.Properties.InputTime.Value;
_excludedApps = _powerAccentSettings.Properties.ExcludedApps.Value;
if (!string.IsNullOrWhiteSpace(_powerAccentSettings.Properties.SelectedLang.Value) && !_powerAccentSettings.Properties.SelectedLang.Value.Contains("ALL"))
{
SelectedLanguageOptions = _powerAccentSettings.Properties.SelectedLang.Value.Split(',')
.Select(l => Languages.Find(lang => lang.LanguageCode == l))
.Where(l => l != null)
.Where(l => l != null) // Wrongly typed languages will appear as null after find. We want to remove those to avoid crashes.
.ToArray();
}
else if (_powerAccentSettings.Properties.SelectedLang.Value.Contains("ALL"))
@@ -191,7 +131,8 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
_toolbarPositionIndex = Array.IndexOf(_toolbarOptions, _powerAccentSettings.Properties.ToolbarPosition.Value);
// Note: OnPropertyChanged calls removed - runs on background thread
// set the callback functions value to handle outgoing IPC message.
SendConfigMSG = ipcMSGCallBackFunc;
}
private void InitializeEnabledValue()

View File

@@ -5,7 +5,6 @@
using System;
using System.IO;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Input;
@@ -18,7 +17,7 @@ using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
namespace Microsoft.PowerToys.Settings.UI.ViewModels
{
public partial class PowerRenameViewModel : Observable, IAsyncInitializable
public partial class PowerRenameViewModel : Observable
{
private GeneralSettings GeneralSettingsConfig { get; set; }
@@ -42,72 +41,6 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
GeneralSettingsConfig = settingsRepository.SettingsConfig;
// Initialize with defaults - heavy I/O deferred to InitializeAsync
Settings = new PowerRenameSettings(new PowerRenameLocalProperties());
// set the callback functions value to handle outgoing IPC message.
SendConfigMSG = ipcMSGCallBackFunc;
// Initialize extension helpers
HeifExtension = new StoreExtensionHelper(
"Microsoft.HEIFImageExtension_8wekyb3d8bbwe",
"ms-windows-store://pdp/?ProductId=9PMMSR1CGPWG",
"HEIF");
AvifExtension = new StoreExtensionHelper(
"Microsoft.AV1VideoExtension_8wekyb3d8bbwe",
"ms-windows-store://pdp/?ProductId=9MVZQVXJBQ9V",
"AV1");
InitializeEnabledValue();
}
/// <summary>
/// Gets a value indicating whether the ViewModel has been initialized.
/// </summary>
public bool IsInitialized { get; private set; }
/// <summary>
/// Gets a value indicating whether initialization is in progress.
/// </summary>
public bool IsLoading { get; private set; }
/// <summary>
/// Initializes the ViewModel asynchronously, loading settings from disk.
/// </summary>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A task representing the async operation.</returns>
public async Task InitializeAsync(CancellationToken cancellationToken = default)
{
if (IsInitialized)
{
return;
}
IsLoading = true;
OnPropertyChanged(nameof(IsLoading));
try
{
await Task.Run(
() =>
{
LoadSettingsFromDisk();
},
cancellationToken);
IsInitialized = true;
}
finally
{
IsLoading = false;
OnPropertyChanged(nameof(IsLoading));
OnPropertyChanged(nameof(IsInitialized));
}
}
private void LoadSettingsFromDisk()
{
try
{
PowerRenameLocalProperties localSettings = _settingsUtils.GetSettingsOrDefault<PowerRenameLocalProperties>(GetSettingsSubPath(), "power-rename-settings.json");
@@ -127,6 +60,9 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
_settingsUtils.SaveSettings(localSettings.ToJsonString(), GetSettingsSubPath(), "power-rename-settings.json");
}
// set the callback functions value to handle outgoing IPC message.
SendConfigMSG = ipcMSGCallBackFunc;
_powerRenameEnabledOnContextMenu = Settings.Properties.ShowIcon.Value;
_powerRenameEnabledOnContextExtendedMenu = Settings.Properties.ExtendedContextMenuOnly.Value;
_powerRenameRestoreFlagsOnLaunch = Settings.Properties.PersistState.Value;
@@ -134,7 +70,18 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
_autoComplete = Settings.Properties.MRUEnabled.Value;
_powerRenameUseBoostLib = Settings.Properties.UseBoostLib.Value;
// Note: OnPropertyChanged calls removed - runs on background thread
// Initialize extension helpers
HeifExtension = new StoreExtensionHelper(
"Microsoft.HEIFImageExtension_8wekyb3d8bbwe",
"ms-windows-store://pdp/?ProductId=9PMMSR1CGPWG",
"HEIF");
AvifExtension = new StoreExtensionHelper(
"Microsoft.AV1VideoExtension_8wekyb3d8bbwe",
"ms-windows-store://pdp/?ProductId=9MVZQVXJBQ9V",
"AV1");
InitializeEnabledValue();
}
private void InitializeEnabledValue()