Compare commits

...

17 Commits

Author SHA1 Message Date
Gordon Lam (SH)
f37754cc61 fix: improve comment wording for spell check 2026-01-27 19:20:57 -08:00
Gordon Lam (SH)
9be06842c1 feat(cmdpal): implement ActionRunner for elevated process execution 2026-01-27 19:10:21 -08:00
Jiří Polášek
08715a6e46 CmdPal: Allow list item context menu (#45086)
## Summary of the Pull Request

This PR enables pointer-invoked context menus for list items when the
list contains at least one item, including the primary item.

The command bar "More" button or Ctrl+K hotkey remain unaffected - the
menu is not accessible through those.

<img width="453" height="210" alt="image"
src="https://github.com/user-attachments/assets/af0f38e3-e1e7-4968-9e2d-6d9293785ab1"
/>


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

- [x] Closes: #45083
<!-- - [ ] 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-27 13:52:12 -06:00
Mike Hall
d26d9f745a CursorWrap improvements (#44936)
## Summary of the Pull Request
- Updated engine for better multi-monitor support.
- Closing the laptop lid will now update the monitor topology
- New settings/dropdown to support wrapping on horizontal, vertical, or
both

<img width="1103" height="643" alt="image"
src="https://github.com/user-attachments/assets/ff4f0835-a8ca-4603-9441-123b71747d5c"
/>

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

- [x] Closes: #44820
- [x] Closes: #44864
- [x] Closes: #44952

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

## Detailed Description of the Pull Request / Additional comments
Feedback for CursorWrap shows that users want the ability to constrain
wrapping for horizontal only, vertical only, or both (default behavior).
This PR adds a new dropdown to CursorWrap settings to enable a user to
select the appropriate wrapping model.

## Validation Steps Performed
Local build and running on Surface Laptop 7 Pro - will also validate on
a multi-monitor setup.

---------

Co-authored-by: vanzue <vanzue@outlook.com>
2026-01-27 13:27:11 +08:00
Gordon Lam
6661adbd5c chore(prompts): add fix active PR comments prompt with scoped changes (#44996)
## Summary of the Pull Request
Enhance the active PR comments prompt to allow for scoped changes while
removing outdated model references from various prompt files.

## PR Checklist
- [ ] **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
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx

## Detailed Description of the Pull Request / Additional comments
The changes include the addition of a new prompt for fixing active PR
comments with scoped changes, ensuring that only simple fixes are
applied. Additionally, references to the model 'GPT-5.1-Codex-Max' have
been removed from several prompt files to streamline the prompts.

## Validation Steps Performed
Manual validation of the new prompt functionality was conducted to
ensure it correctly identifies and resolves active PR comments.
```
2026-01-26 20:34:11 -08:00
Heiko
5ecb97b4e0 [Enterprise; Policy] Add policy for CursorWrap to ADMX (#45028)
<!-- 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

Added missing policy definition.

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

- [x] Closes: #44897
<!-- - [ ] 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
- [ ] **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)
- [x] **Documentation updated:** See PR for issue #44484 

<!-- 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-27 11:24:02 +08:00
Jiří Polášek
13ce5db6b1 CmdPal: Add solution filter for Microsoft.CmdPal.Ext.PowerToys (#45096)
## Summary of the Pull Request

This PR adds a new solution filter (.slnf) for the
Microsoft.CmdPal.Ext.PowerToys extension project and its dependencies.

This is added as a separate solution filter alongside
CommandPalette.slnf, since the extension is not directly dependent on
Command Palette. Instead, it relies on the public SDK distributed via
NuGet. It also depends on other PowerToys projects, which would
unnecessarily clutter CommandPalette.slnf.

<!-- 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-26 21:14:05 -06:00
Jiří Polášek
f0831742d6 CmdPal: Remove deadlock bait from AppListItem (#45076)
## Summary of the Pull Request

This PR removes a Task.Wait() call from lazy-loading AppListItem details
that could be invoked on the UI thread and lead to a deadlock.

It now follows the same pattern previously used for loading icons in the
same class, which has proven to work well.

Prevents #44938 from stepping on this landmine.

Cherry-picked from #44973.

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

- [x] Closes: #45074 
<!-- - [ ] 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-27 09:51:16 +08:00
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
47 changed files with 2256 additions and 1035 deletions

View File

@@ -22,7 +22,6 @@ ADate
ADDSTRING
ADDUNDORECORD
ADifferent
adjacents
ADMINS
adml
admx
@@ -103,7 +102,6 @@ ASYNCWINDOWPLACEMENT
ASYNCWINDOWPOS
atl
ATRIOX
ATX
aumid
authenticode
AUTOBUDDY
@@ -220,10 +218,11 @@ CIELCh
cim
CImage
cla
claude
CLASSDC
classguid
classmethod
CLASSNOTAVAILABLE
claude
CLEARTYPE
clickable
clickonce
@@ -262,7 +261,6 @@ colorhistory
colorhistorylimit
COLORKEY
colorref
Convs
comctl
comdlg
comexp
@@ -283,6 +281,7 @@ CONTEXTHELP
CONTEXTMENUHANDLER
contractversion
CONTROLPARENT
Convs
copiedcolorrepresentation
coppied
copyable
@@ -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
@@ -350,12 +348,14 @@ datareader
datatracker
dataversion
Dayof
dbcc
DBID
DBLCLKS
DBLEPSILON
DBPROP
DBPROPIDSET
DBPROPSET
DBT
DCBA
DCOM
DComposition
@@ -373,8 +373,7 @@ DEFAULTICON
defaultlib
DEFAULTONLY
DEFAULTSIZE
DEFAULTTONEAREST
Defaulttonearest
defaulttonearest
DEFAULTTONULL
DEFAULTTOPRIMARY
DEFERERASE
@@ -396,14 +395,19 @@ DESKTOPVERTRES
devblogs
devdocs
devenv
DEVICEINTERFACE
devicetype
DEVINTERFACE
devmgmt
DEVMODE
DEVMODEW
DEVNODES
devpal
DEVTYP
dfx
DIALOGEX
digicert
diffs
digicert
DINORMAL
DISABLEASACTIONKEY
DISABLENOSCROLL
@@ -546,7 +550,6 @@ fdx
FErase
fesf
FFFF
FInc
Figma
FILEEXPLORER
fileexploreraddons
@@ -567,6 +570,7 @@ FILESYSPATH
Filetime
FILEVERSION
FILTERMODE
FInc
findfast
findmymouse
FIXEDFILEINFO
@@ -668,13 +672,14 @@ HCRYPTPROV
hcursor
hcwhite
hdc
HDEVNOTIFY
hdr
hdrop
hdwwiz
Helpline
helptext
HGFE
hgdiobj
HGFE
hglobal
hhk
HHmmssfff
@@ -750,9 +755,9 @@ HWNDPARENT
HWNDPREV
hyjiacan
IAI
icf
ICONERROR
ICONLOCATION
icf
IDCANCEL
IDD
idk
@@ -843,8 +848,8 @@ jeli
jfif
jgeosdfsdsgmkedfgdfgdfgbkmhcgcflmi
jjw
JOBOBJECT
jobject
JOBOBJECT
jpe
jpnime
Jsons
@@ -888,6 +893,7 @@ Ldr
LEFTALIGN
LEFTSCROLLBAR
LEFTTEXT
leftclick
LError
LEVELID
LExit
@@ -930,9 +936,9 @@ LOWORD
lparam
LPBITMAPINFOHEADER
LPCFHOOKPROC
lpch
LPCITEMIDLIST
LPCLSID
lpch
lpcmi
LPCMINVOKECOMMANDINFO
LPCREATESTRUCT
@@ -948,6 +954,7 @@ LPMONITORINFO
LPOSVERSIONINFOEXW
LPQUERY
lprc
LPrivate
LPSAFEARRAY
lpstr
lpsz
@@ -957,7 +964,6 @@ lptpm
LPTR
LPTSTR
lpv
LPrivate
LPW
lpwcx
lpwndpl
@@ -1001,18 +1007,19 @@ mber
MBM
MBR
Mbuttondown
mcp
MDICHILD
MDL
mdtext
mdtxt
mdwn
meme
mcp
memicmp
MENUITEMINFO
MENUITEMINFOW
MERGECOPY
MERGEPAINT
Metacharacter
metadatamatters
Metadatas
metafile
@@ -1042,8 +1049,8 @@ mmi
mmsys
mobileredirect
mockapi
modelcontextprotocol
MODALFRAME
modelcontextprotocol
MODESPRUNED
MONITORENUMPROC
MONITORINFO
@@ -1087,9 +1094,9 @@ MSLLHOOKSTRUCT
Mso
msrc
msstore
mstsc
msvcp
MT
mstsc
MTND
MULTIPLEUSE
multizone
@@ -1099,11 +1106,11 @@ muxxc
muxxh
MVPs
mvvm
myorg
myrepo
MVVMTK
MWBEx
MYICON
myorg
myrepo
NAMECHANGE
namespaceanddescendants
nao
@@ -1244,10 +1251,8 @@ opencode
OPENFILENAME
openrdp
opensource
openxmlformats
ollama
onnx
openurl
openxmlformats
OPTIMIZEFORINVOKE
ORPHANEDDIALOGTITLE
ORSCANS
@@ -1464,7 +1469,6 @@ rbhid
Rbuttondown
rclsid
RCZOOMIT
remotedesktop
rdp
RDW
READMODE
@@ -1493,6 +1497,7 @@ remappings
REMAPSUCCESSFUL
REMAPUNSUCCESSFUL
Remotable
remotedesktop
remoteip
Removelnk
renamable
@@ -1526,8 +1531,8 @@ RIGHTSCROLLBAR
riid
RKey
RNumber
rop
rollups
rop
ROUNDSMALL
ROWSETEXT
rpcrt
@@ -1760,15 +1765,13 @@ SUBMODULEUPDATE
subresource
Superbar
sut
swe
svchost
SVGIn
SVGIO
svgz
SVSI
SWFO
SWP
Swp
swp
SWPNOSIZE
SWPNOZORDER
SWRESTORE
@@ -1787,8 +1790,7 @@ SYSKEY
syskeydown
SYSKEYUP
SYSLIB
SYSMENU
Sysmenu
sysmenu
systemai
SYSTEMAPPS
SYSTEMMODAL
@@ -1892,9 +1894,9 @@ uitests
UITo
ULONGLONG
Ultrawide
ums
UMax
UMin
ums
uncompilable
UNCPRIORITY
UNDNAME

View File

@@ -1,6 +1,5 @@
---
agent: 'agent'
model: 'GPT-5.1-Codex-Max'
description: 'Generate an 80-character git commit title for the local diff'
---

View File

@@ -1,6 +1,5 @@
---
agent: 'agent'
model: 'GPT-5.1-Codex-Max'
description: 'Generate a PowerToys-ready pull request description from the local diff'
---

View File

@@ -1,6 +1,5 @@
---
agent: 'agent'
model: 'GPT-5.1-Codex-Max'
description: 'Execute the fix for a GitHub issue using the previously generated implementation plan'
---

View File

@@ -0,0 +1,70 @@
---
description: 'Fix active pull request comments with scoped changes'
name: 'fix-pr-active-comments'
agent: 'agent'
argument-hint: 'PR number or active PR URL'
---
# Fix Active PR Comments
## Mission
Resolve active pull request comments by applying only simple fixes. For complex refactors, write a plan instead of changing code.
## Scope & Preconditions
- You must have an active pull request context or a provided PR number.
- Only implement simple changes. Do not implement large refactors.
- If required context is missing, request it and stop.
## Inputs
- Required: ${input:pr_number:PR number or URL}
- Optional: ${input:comment_scope:files or areas to focus on}
- Optional: ${input:fixing_guidelines:additional fixing guidelines from the user}
## Workflow
1. Locate all active (unresolved) PR review comments for the given PR.
2. For each comment, classify the change scope:
- Simple change: limited edits, localized fix, low risk, no broad redesign.
- Large refactor: multi-file redesign, architecture change, or risky behavior change.
3. For each large refactor request:
- Do not modify code.
- Write a planning document to Generated Files/prReview/${input:pr_number}/fixPlan/.
4. For each simple change request:
- Implement the fix with minimal edits.
- Run quick checks if needed.
- Commit and push the change.
5. For comments that seem invalid, unclear, or not applicable (even if simple):
- Do not change code.
- Add the item to a summary table in Generated Files/prReview/${input:pr_number}/fixPlan/overview.md.
- Consult back to the end user in a friendly, polite tone.
6. Respond to each comment that you fixed:
- Reply in the active conversation.
- Use a polite or friendly tone.
- Keep the response under 200 words.
- Resolve the comment after replying.
## Output Expectations
- Simple fixes: code changes committed and pushed.
- Large refactors: a plan file saved to Generated Files/prReview/${input:pr_number}/fixPlan/.
- Invalid or unclear comments: captured in Generated Files/prReview/${input:pr_number}/fixPlan/overview.md.
- Each fixed comment has a reply under 200 words and is resolved.
## Plan File Template
Use this template for each large refactor item:
# Fix Plan: <short title>
## Context
- Comment link:
- Impacted areas:
## Overview Table Template
Use this table in Generated Files/prReview/${input:pr_number}/fixPlan/overview.md:
| Comment link | Summary | Reason not applied | Suggested follow-up |
| --- | --- | --- | --- |
| | | | |
## Quality Assurance
- Verify plan file path exists.
- Ensure no code changes were made for large refactor items.
- Confirm replies are under 200 words and comments are resolved.

View File

@@ -1,6 +1,5 @@
---
agent: 'agent'
model: 'GPT-5.1-Codex-Max'
description: 'Resolve Code scanning / check-spelling comments on the active PR'
---

View File

@@ -1,6 +1,5 @@
---
agent: 'agent'
model: 'GPT-5.1-Codex-Max'
description: 'Review a GitHub issue, score it (0-100), and generate an implementation plan'
---

View File

@@ -1,6 +1,5 @@
---
agent: 'agent'
model: 'GPT-5.1-Codex-Max'
description: 'Perform a comprehensive PR review with per-step Markdown and machine-readable outputs'
---

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

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

@@ -104,6 +104,66 @@ int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
}
}
}
else if (action == RUN_AS_USER || action == RUN_AS_ADMIN)
{
// Handle "Run as different user" and "Run as administrator" actions.
// This is used by Command Palette to work around WinUI3/MSIX packaging limitations
// where ShellExecute with "runas user"/"runas" verbs doesn't work properly from packaged apps.
int nextArg = 2;
std::wstring_view target;
std::wstring_view workingDir;
while (nextArg < nArgs)
{
if (std::wstring_view(args[nextArg]) == L"-target" && nextArg + 1 < nArgs)
{
target = args[nextArg + 1];
nextArg += 2;
}
else if (std::wstring_view(args[nextArg]) == L"-workingDir" && nextArg + 1 < nArgs)
{
workingDir = args[nextArg + 1];
nextArg += 2;
}
else
{
nextArg++;
}
}
if (target.empty())
{
Logger::error(L"ActionRunner: {} called without -target argument", action);
return 1;
}
Logger::trace(L"ActionRunner: {} target='{}' workingDir='{}'", action, target, workingDir);
SHELLEXECUTEINFOW sei = { sizeof(sei) };
sei.fMask = SEE_MASK_FLAG_NO_UI;
sei.lpFile = target.data();
sei.lpDirectory = workingDir.empty() ? nullptr : workingDir.data();
sei.lpVerb = (action == RUN_AS_ADMIN) ? L"runas" : L"runasuser";
sei.nShow = SW_SHOWNORMAL;
if (!ShellExecuteExW(&sei))
{
DWORD error = GetLastError();
if (error == ERROR_CANCELLED)
{
// User cancelled the UAC/credential dialog - this is expected behavior
Logger::trace(L"ActionRunner: User cancelled {} dialog for '{}'", action, target);
}
else
{
Logger::error(L"ActionRunner: ShellExecuteEx failed for {} '{}': error {}", action, target, error);
}
return static_cast<int>(error);
}
Logger::trace(L"ActionRunner: Successfully launched '{}' with {}", target, action);
}
return 0;
}

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

@@ -1,11 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (c) Microsoft Corporation.
Licensed under the MIT License. -->
<policyDefinitions xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" revision="1.18" schemaVersion="1.0" xmlns="http://schemas.microsoft.com/GroupPolicy/2006/07/PolicyDefinitions">
<policyDefinitions xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" revision="1.19" schemaVersion="1.0" xmlns="http://schemas.microsoft.com/GroupPolicy/2006/07/PolicyDefinitions">
<policyNamespaces>
<target prefix="powertoys" namespace="Microsoft.Policies.PowerToys" />
</policyNamespaces>
<resources minRequiredRevision="1.18"/><!-- Last changed with PowerToys v0.96.0 -->
<resources minRequiredRevision="1.19"/><!-- Last changed with PowerToys v0.97.0 -->
<supportedOn>
<definitions>
<definition name="SUPPORTED_POWERTOYS_0_64_0" displayName="$(string.SUPPORTED_POWERTOYS_0_64_0)"/>
@@ -27,6 +27,7 @@
<definition name="SUPPORTED_POWERTOYS_0_89_0" displayName="$(string.SUPPORTED_POWERTOYS_0_89_0)"/>
<definition name="SUPPORTED_POWERTOYS_0_90_0" displayName="$(string.SUPPORTED_POWERTOYS_0_90_0)"/>
<definition name="SUPPORTED_POWERTOYS_0_96_0" displayName="$(string.SUPPORTED_POWERTOYS_0_96_0)"/>
<definition name="SUPPORTED_POWERTOYS_0_97_0" displayName="$(string.SUPPORTED_POWERTOYS_0_97_0)"/>
<definition name="SUPPORTED_POWERTOYS_0_64_0_TO_0_87_1" displayName="$(string.SUPPORTED_POWERTOYS_0_64_0_TO_0_87_1)"/>
</definitions>
</supportedOn>
@@ -338,6 +339,16 @@
<decimal value="0" />
</disabledValue>
</policy>
<policy name="ConfigureEnabledUtilityCursorWrap" class="Both" displayName="$(string.ConfigureEnabledUtilityCursorWrap)" explainText="$(string.ConfigureEnabledUtilityDescription)" key="Software\Policies\PowerToys" valueName="ConfigureEnabledUtilityCursorWrap">
<parentCategory ref="PowerToys" />
<supportedOn ref="SUPPORTED_POWERTOYS_0_97_0" />
<enabledValue>
<decimal value="1" />
</enabledValue>
<disabledValue>
<decimal value="0" />
</disabledValue>
</policy>
<policy name="ConfigureEnabledUtilityFindMyMouse" class="Both" displayName="$(string.ConfigureEnabledUtilityFindMyMouse)" explainText="$(string.ConfigureEnabledUtilityDescription)" key="Software\Policies\PowerToys" valueName="ConfigureEnabledUtilityFindMyMouse">
<parentCategory ref="PowerToys" />
<supportedOn ref="SUPPORTED_POWERTOYS_0_64_0" />

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (c) Microsoft Corporation.
Licensed under the MIT License. -->
<policyDefinitionResources xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" revision="1.18" schemaVersion="1.0" xmlns="http://schemas.microsoft.com/GroupPolicy/2006/07/PolicyDefinitions">
<policyDefinitionResources xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" revision="1.19" schemaVersion="1.0" xmlns="http://schemas.microsoft.com/GroupPolicy/2006/07/PolicyDefinitions">
<displayName>PowerToys</displayName>
<description>PowerToys</description>
<resources>
@@ -34,6 +34,7 @@
<string id="SUPPORTED_POWERTOYS_0_89_0">PowerToys version 0.89.0 or later</string>
<string id="SUPPORTED_POWERTOYS_0_90_0">PowerToys version 0.90.0 or later</string>
<string id="SUPPORTED_POWERTOYS_0_96_0">PowerToys version 0.96.0 or later</string>
<string id="SUPPORTED_POWERTOYS_0_97_0">PowerToys version 0.97.0 or later</string>
<string id="SUPPORTED_POWERTOYS_0_64_0_TO_0_87_1">From PowerToys version 0.64.0 until PowerToys version 0.87.1</string>
<string id="ConfigureAllUtilityGlobalEnabledStateDescription">This policy configures the enabled state for all PowerToys utilities.
@@ -266,6 +267,7 @@ If you don't configure this policy, the user will be able to control the setting
<string id="ConfigureEnabledUtilityKeyboardManager">Keyboard Manager: Configure enabled state</string>
<string id="ConfigureEnabledUtilityFindMyMouse">Find My Mouse: Configure enabled state</string>
<string id="ConfigureEnabledUtilityMouseHighlighter">Mouse Highlighter: Configure enabled state</string>
<string id="ConfigureEnabledUtilityCursorWrap">CursorWrap: Configure enabled state</string>
<string id="ConfigureEnabledUtilityMouseJump">Mouse Jump: Configure enabled state</string>
<string id="ConfigureEnabledUtilityMousePointerCrosshairs">Mouse Pointer Crosshairs: Configure enabled state</string>
<string id="ConfigureEnabledUtilityMouseWithoutBorders">Mouse Without Borders: Configure enabled state</string>

View File

@@ -84,14 +84,17 @@
</ClCompile>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="CursorWrapCore.h" />
<ClInclude Include="CursorWrapTests.h" />
<ClInclude Include="MonitorTopology.h" />
<ClInclude Include="pch.h" />
<ClInclude Include="trace.h" />
<ClInclude Include="resource.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="CursorWrapCore.cpp" />
<ClCompile Include="dllmain.cpp" />
<ClCompile Include="MonitorTopology.cpp" />
<ClCompile Include="pch.cpp">
<PrecompiledHeader Condition="'$(UsePrecompiledHeaders)' != 'false'">Create</PrecompiledHeader>
</ClCompile>

View File

@@ -0,0 +1,268 @@
// 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.
#include "pch.h"
#include "CursorWrapCore.h"
#include "../../../common/logger/logger.h"
#include <sstream>
#include <iomanip>
#include <ctime>
CursorWrapCore::CursorWrapCore()
{
}
#ifdef _DEBUG
std::wstring CursorWrapCore::GenerateTopologyJSON() const
{
std::wostringstream json;
// Get current time
auto now = std::time(nullptr);
std::tm tm{};
localtime_s(&tm, &now);
wchar_t computerName[MAX_COMPUTERNAME_LENGTH + 1] = {0};
DWORD size = MAX_COMPUTERNAME_LENGTH + 1;
GetComputerNameW(computerName, &size);
wchar_t userName[256] = {0};
size = 256;
GetUserNameW(userName, &size);
json << L"{\n";
json << L" \"captured_at\": \"" << std::put_time(&tm, L"%Y-%m-%dT%H:%M:%S%z") << L"\",\n";
json << L" \"computer_name\": \"" << computerName << L"\",\n";
json << L" \"user_name\": \"" << userName << L"\",\n";
json << L" \"monitor_count\": " << m_monitors.size() << L",\n";
json << L" \"monitors\": [\n";
for (size_t i = 0; i < m_monitors.size(); ++i)
{
const auto& monitor = m_monitors[i];
// Get DPI for this monitor
UINT dpiX = 96, dpiY = 96;
POINT center = {
(monitor.rect.left + monitor.rect.right) / 2,
(monitor.rect.top + monitor.rect.bottom) / 2
};
HMONITOR hMon = MonitorFromPoint(center, MONITOR_DEFAULTTONEAREST);
if (hMon)
{
// Try GetDpiForMonitor (requires linking Shcore.lib)
using GetDpiForMonitorFunc = HRESULT (WINAPI *)(HMONITOR, int, UINT*, UINT*);
HMODULE shcore = LoadLibraryW(L"Shcore.dll");
if (shcore)
{
auto getDpi = reinterpret_cast<GetDpiForMonitorFunc>(GetProcAddress(shcore, "GetDpiForMonitor"));
if (getDpi)
{
getDpi(hMon, 0, &dpiX, &dpiY); // MDT_EFFECTIVE_DPI = 0
}
FreeLibrary(shcore);
}
}
int scalingPercent = static_cast<int>((dpiX / 96.0) * 100);
json << L" {\n";
json << L" \"left\": " << monitor.rect.left << L",\n";
json << L" \"top\": " << monitor.rect.top << L",\n";
json << L" \"right\": " << monitor.rect.right << L",\n";
json << L" \"bottom\": " << monitor.rect.bottom << L",\n";
json << L" \"width\": " << (monitor.rect.right - monitor.rect.left) << L",\n";
json << L" \"height\": " << (monitor.rect.bottom - monitor.rect.top) << L",\n";
json << L" \"dpi\": " << dpiX << L",\n";
json << L" \"scaling_percent\": " << scalingPercent << L",\n";
json << L" \"primary\": " << (monitor.isPrimary ? L"true" : L"false") << L",\n";
json << L" \"monitor_id\": " << monitor.monitorId << L"\n";
json << L" }";
if (i < m_monitors.size() - 1)
{
json << L",";
}
json << L"\n";
}
json << L" ]\n";
json << L"}";
return json.str();
}
#endif
void CursorWrapCore::UpdateMonitorInfo()
{
size_t previousMonitorCount = m_monitors.size();
Logger::info(L"======= UPDATE MONITOR INFO START =======");
Logger::info(L"Previous monitor count: {}", previousMonitorCount);
m_monitors.clear();
EnumDisplayMonitors(nullptr, nullptr, [](HMONITOR hMonitor, HDC, LPRECT, LPARAM lParam) -> BOOL {
auto* self = reinterpret_cast<CursorWrapCore*>(lParam);
MONITORINFO mi{};
mi.cbSize = sizeof(MONITORINFO);
if (GetMonitorInfo(hMonitor, &mi))
{
MonitorInfo info{};
info.hMonitor = hMonitor; // Store handle for direct comparison later
info.rect = mi.rcMonitor;
info.isPrimary = (mi.dwFlags & MONITORINFOF_PRIMARY) != 0;
info.monitorId = static_cast<int>(self->m_monitors.size());
self->m_monitors.push_back(info);
Logger::info(L"Enumerated monitor {}: hMonitor={}, rect=({},{},{},{}), primary={}",
info.monitorId, reinterpret_cast<uintptr_t>(hMonitor),
mi.rcMonitor.left, mi.rcMonitor.top, mi.rcMonitor.right, mi.rcMonitor.bottom,
info.isPrimary ? L"yes" : L"no");
}
return TRUE;
}, reinterpret_cast<LPARAM>(this));
if (previousMonitorCount != m_monitors.size())
{
Logger::info(L"*** MONITOR CONFIGURATION CHANGED: {} -> {} monitors ***",
previousMonitorCount, m_monitors.size());
}
m_topology.Initialize(m_monitors);
// Log monitor configuration summary
Logger::info(L"Monitor configuration updated: {} monitor(s)", m_monitors.size());
for (size_t i = 0; i < m_monitors.size(); ++i)
{
const auto& m = m_monitors[i];
int width = m.rect.right - m.rect.left;
int height = m.rect.bottom - m.rect.top;
Logger::info(L" Monitor {}: {}x{} at ({}, {}){}",
i, width, height, m.rect.left, m.rect.top,
m.isPrimary ? L" [PRIMARY]" : L"");
}
Logger::info(L" Detected {} outer edges for cursor wrapping", m_topology.GetOuterEdges().size());
// Detect and log monitor gaps
auto gaps = m_topology.DetectMonitorGaps();
if (!gaps.empty())
{
Logger::warn(L"Monitor configuration has coordinate gaps that may prevent wrapping:");
for (const auto& gap : gaps)
{
Logger::warn(L" Gap between Monitor {} and Monitor {}: {}px horizontal gap, {}px vertical overlap",
gap.monitor1Index, gap.monitor2Index, gap.horizontalGap, gap.verticalOverlap);
}
Logger::warn(L" If monitors appear snapped in Display Settings but show gaps here:");
Logger::warn(L" 1. Try dragging monitors apart and snapping them back together");
Logger::warn(L" 2. Update your GPU drivers");
}
Logger::info(L"======= UPDATE MONITOR INFO END =======");
}
POINT CursorWrapCore::HandleMouseMove(const POINT& currentPos, bool disableWrapDuringDrag, int wrapMode)
{
// Check if wrapping should be disabled during drag
if (disableWrapDuringDrag && (GetAsyncKeyState(VK_LBUTTON) & 0x8000))
{
#ifdef _DEBUG
OutputDebugStringW(L"[CursorWrap] [DRAG] Left mouse button down - skipping wrap\n");
#endif
return currentPos;
}
// Convert int wrapMode to WrapMode enum
WrapMode mode = static_cast<WrapMode>(wrapMode);
#ifdef _DEBUG
{
std::wostringstream oss;
oss << L"[CursorWrap] [MOVE] Cursor at (" << currentPos.x << L", " << currentPos.y << L")";
// Get current monitor and identify which one
HMONITOR currentMonitor = MonitorFromPoint(currentPos, MONITOR_DEFAULTTONEAREST);
RECT monitorRect;
if (m_topology.GetMonitorRect(currentMonitor, monitorRect))
{
// Find monitor ID
int monitorId = -1;
for (const auto& monitor : m_monitors)
{
if (monitor.rect.left == monitorRect.left &&
monitor.rect.top == monitorRect.top &&
monitor.rect.right == monitorRect.right &&
monitor.rect.bottom == monitorRect.bottom)
{
monitorId = monitor.monitorId;
break;
}
}
oss << L" on Monitor " << monitorId << L" [" << monitorRect.left << L".." << monitorRect.right
<< L", " << monitorRect.top << L".." << monitorRect.bottom << L"]";
}
else
{
oss << L" (beyond monitor bounds)";
}
oss << L"\n";
OutputDebugStringW(oss.str().c_str());
}
#endif
// Get current monitor
HMONITOR currentMonitor = MonitorFromPoint(currentPos, MONITOR_DEFAULTTONEAREST);
// Check if cursor is on an outer edge (filtered by wrap mode)
EdgeType edgeType;
if (!m_topology.IsOnOuterEdge(currentMonitor, currentPos, edgeType, mode))
{
#ifdef _DEBUG
static bool lastWasNotOuter = false;
if (!lastWasNotOuter)
{
OutputDebugStringW(L"[CursorWrap] [MOVE] Not on outer edge - no wrapping\n");
lastWasNotOuter = true;
}
#endif
return currentPos; // Not on an outer edge
}
#ifdef _DEBUG
{
const wchar_t* edgeStr = L"Unknown";
switch (edgeType)
{
case EdgeType::Left: edgeStr = L"Left"; break;
case EdgeType::Right: edgeStr = L"Right"; break;
case EdgeType::Top: edgeStr = L"Top"; break;
case EdgeType::Bottom: edgeStr = L"Bottom"; break;
}
std::wostringstream oss;
oss << L"[CursorWrap] [EDGE] Detected outer " << edgeStr << L" edge at (" << currentPos.x << L", " << currentPos.y << L")\n";
OutputDebugStringW(oss.str().c_str());
}
#endif
// Calculate wrap destination
POINT newPos = m_topology.GetWrapDestination(currentMonitor, currentPos, edgeType);
#ifdef _DEBUG
if (newPos.x != currentPos.x || newPos.y != currentPos.y)
{
std::wostringstream oss;
oss << L"[CursorWrap] [WRAP] Position change: (" << currentPos.x << L", " << currentPos.y
<< L") -> (" << newPos.x << L", " << newPos.y << L")\n";
oss << L"[CursorWrap] [WRAP] Delta: (" << (newPos.x - currentPos.x) << L", " << (newPos.y - currentPos.y) << L")\n";
OutputDebugStringW(oss.str().c_str());
}
else
{
OutputDebugStringW(L"[CursorWrap] [WRAP] No position change (same-monitor wrap?)\n");
}
#endif
return newPos;
}

View File

@@ -0,0 +1,33 @@
// 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.
#pragma once
#include <windows.h>
#include <vector>
#include <string>
#include "MonitorTopology.h"
// Core cursor wrapping engine
class CursorWrapCore
{
public:
CursorWrapCore();
void UpdateMonitorInfo();
// Handle mouse move with wrap mode filtering
// wrapMode: 0=Both, 1=VerticalOnly, 2=HorizontalOnly
POINT HandleMouseMove(const POINT& currentPos, bool disableWrapDuringDrag, int wrapMode);
const std::vector<MonitorInfo>& GetMonitors() const { return m_monitors; }
const MonitorTopology& GetTopology() const { return m_topology; }
private:
#ifdef _DEBUG
std::wstring GenerateTopologyJSON() const;
#endif
std::vector<MonitorInfo> m_monitors;
MonitorTopology m_topology;
};

View File

@@ -0,0 +1,546 @@
// 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.
#include "pch.h"
#include "MonitorTopology.h"
#include "../../../common/logger/logger.h"
#include <algorithm>
#include <cmath>
void MonitorTopology::Initialize(const std::vector<MonitorInfo>& monitors)
{
Logger::info(L"======= TOPOLOGY INITIALIZATION START =======");
Logger::info(L"Initializing edge-based topology for {} monitors", monitors.size());
m_monitors = monitors;
m_outerEdges.clear();
m_edgeMap.clear();
if (monitors.empty())
{
Logger::warn(L"No monitors provided to Initialize");
return;
}
// Log monitor details
for (size_t i = 0; i < monitors.size(); ++i)
{
const auto& m = monitors[i];
Logger::info(L"Monitor {}: hMonitor={}, rect=({},{},{},{}), primary={}",
i, reinterpret_cast<uintptr_t>(m.hMonitor),
m.rect.left, m.rect.top, m.rect.right, m.rect.bottom,
m.isPrimary ? L"yes" : L"no");
}
BuildEdgeMap();
IdentifyOuterEdges();
Logger::info(L"Found {} outer edges", m_outerEdges.size());
for (const auto& edge : m_outerEdges)
{
const wchar_t* typeStr = L"Unknown";
switch (edge.type)
{
case EdgeType::Left: typeStr = L"Left"; break;
case EdgeType::Right: typeStr = L"Right"; break;
case EdgeType::Top: typeStr = L"Top"; break;
case EdgeType::Bottom: typeStr = L"Bottom"; break;
}
Logger::info(L"Outer edge: Monitor {} {} at position {}, range [{}, {}]",
edge.monitorIndex, typeStr, edge.position, edge.start, edge.end);
}
Logger::info(L"======= TOPOLOGY INITIALIZATION COMPLETE =======");
}
void MonitorTopology::BuildEdgeMap()
{
// Create edges for each monitor using monitor index (not HMONITOR)
// This is important because HMONITOR handles can change when monitors are
// added/removed dynamically, but indices remain stable within a single
// topology configuration
for (size_t idx = 0; idx < m_monitors.size(); ++idx)
{
const auto& monitor = m_monitors[idx];
int monitorIndex = static_cast<int>(idx);
// Left edge
MonitorEdge leftEdge;
leftEdge.monitorIndex = monitorIndex;
leftEdge.type = EdgeType::Left;
leftEdge.position = monitor.rect.left;
leftEdge.start = monitor.rect.top;
leftEdge.end = monitor.rect.bottom;
leftEdge.isOuter = true; // Will be updated in IdentifyOuterEdges
m_edgeMap[{monitorIndex, EdgeType::Left}] = leftEdge;
// Right edge
MonitorEdge rightEdge;
rightEdge.monitorIndex = monitorIndex;
rightEdge.type = EdgeType::Right;
rightEdge.position = monitor.rect.right - 1;
rightEdge.start = monitor.rect.top;
rightEdge.end = monitor.rect.bottom;
rightEdge.isOuter = true;
m_edgeMap[{monitorIndex, EdgeType::Right}] = rightEdge;
// Top edge
MonitorEdge topEdge;
topEdge.monitorIndex = monitorIndex;
topEdge.type = EdgeType::Top;
topEdge.position = monitor.rect.top;
topEdge.start = monitor.rect.left;
topEdge.end = monitor.rect.right;
topEdge.isOuter = true;
m_edgeMap[{monitorIndex, EdgeType::Top}] = topEdge;
// Bottom edge
MonitorEdge bottomEdge;
bottomEdge.monitorIndex = monitorIndex;
bottomEdge.type = EdgeType::Bottom;
bottomEdge.position = monitor.rect.bottom - 1;
bottomEdge.start = monitor.rect.left;
bottomEdge.end = monitor.rect.right;
bottomEdge.isOuter = true;
m_edgeMap[{monitorIndex, EdgeType::Bottom}] = bottomEdge;
}
}
void MonitorTopology::IdentifyOuterEdges()
{
const int tolerance = 50;
// Check each edge against all other edges to find adjacent ones
for (auto& [key1, edge1] : m_edgeMap)
{
for (const auto& [key2, edge2] : m_edgeMap)
{
if (edge1.monitorIndex == edge2.monitorIndex)
{
continue; // Same monitor
}
// Check if edges are adjacent
if (EdgesAreAdjacent(edge1, edge2, tolerance))
{
edge1.isOuter = false;
break; // This edge has an adjacent monitor
}
}
if (edge1.isOuter)
{
m_outerEdges.push_back(edge1);
}
}
}
bool MonitorTopology::EdgesAreAdjacent(const MonitorEdge& edge1, const MonitorEdge& edge2, int tolerance) const
{
// Edges must be opposite types to be adjacent
bool oppositeTypes = false;
if ((edge1.type == EdgeType::Left && edge2.type == EdgeType::Right) ||
(edge1.type == EdgeType::Right && edge2.type == EdgeType::Left) ||
(edge1.type == EdgeType::Top && edge2.type == EdgeType::Bottom) ||
(edge1.type == EdgeType::Bottom && edge2.type == EdgeType::Top))
{
oppositeTypes = true;
}
if (!oppositeTypes)
{
return false;
}
// Check if positions are within tolerance
if (abs(edge1.position - edge2.position) > tolerance)
{
return false;
}
// Check if perpendicular ranges overlap
int overlapStart = max(edge1.start, edge2.start);
int overlapEnd = min(edge1.end, edge2.end);
return overlapEnd > overlapStart + tolerance;
}
bool MonitorTopology::IsOnOuterEdge(HMONITOR monitor, const POINT& cursorPos, EdgeType& outEdgeType, WrapMode wrapMode) const
{
RECT monitorRect;
if (!GetMonitorRect(monitor, monitorRect))
{
Logger::warn(L"IsOnOuterEdge: GetMonitorRect failed for monitor handle {}", reinterpret_cast<uintptr_t>(monitor));
return false;
}
// Get monitor index for edge map lookup
int monitorIndex = GetMonitorIndex(monitor);
if (monitorIndex < 0)
{
Logger::warn(L"IsOnOuterEdge: Monitor index not found for handle {} at cursor ({}, {})",
reinterpret_cast<uintptr_t>(monitor), cursorPos.x, cursorPos.y);
return false; // Monitor not found in our list
}
// Check each edge type
const int edgeThreshold = 1;
// At corners, multiple edges may match - collect all candidates and try each
// to find one with a valid wrap destination
std::vector<EdgeType> candidateEdges;
// Left edge - only if mode allows horizontal wrapping
if ((wrapMode == WrapMode::Both || wrapMode == WrapMode::HorizontalOnly) &&
cursorPos.x <= monitorRect.left + edgeThreshold)
{
auto it = m_edgeMap.find({monitorIndex, EdgeType::Left});
if (it != m_edgeMap.end() && it->second.isOuter)
{
candidateEdges.push_back(EdgeType::Left);
}
}
// Right edge - only if mode allows horizontal wrapping
if ((wrapMode == WrapMode::Both || wrapMode == WrapMode::HorizontalOnly) &&
cursorPos.x >= monitorRect.right - 1 - edgeThreshold)
{
auto it = m_edgeMap.find({monitorIndex, EdgeType::Right});
if (it != m_edgeMap.end())
{
if (it->second.isOuter)
{
candidateEdges.push_back(EdgeType::Right);
}
// Debug: Log why right edge isn't outer
else
{
Logger::trace(L"IsOnOuterEdge: Monitor {} right edge is NOT outer (inner edge)", monitorIndex);
}
}
}
// Top edge - only if mode allows vertical wrapping
if ((wrapMode == WrapMode::Both || wrapMode == WrapMode::VerticalOnly) &&
cursorPos.y <= monitorRect.top + edgeThreshold)
{
auto it = m_edgeMap.find({monitorIndex, EdgeType::Top});
if (it != m_edgeMap.end() && it->second.isOuter)
{
candidateEdges.push_back(EdgeType::Top);
}
}
// Bottom edge - only if mode allows vertical wrapping
if ((wrapMode == WrapMode::Both || wrapMode == WrapMode::VerticalOnly) &&
cursorPos.y >= monitorRect.bottom - 1 - edgeThreshold)
{
auto it = m_edgeMap.find({monitorIndex, EdgeType::Bottom});
if (it != m_edgeMap.end() && it->second.isOuter)
{
candidateEdges.push_back(EdgeType::Bottom);
}
}
if (candidateEdges.empty())
{
return false;
}
// Try each candidate edge and return first with valid wrap destination
for (EdgeType candidate : candidateEdges)
{
MonitorEdge oppositeEdge = FindOppositeOuterEdge(candidate,
(candidate == EdgeType::Left || candidate == EdgeType::Right) ? cursorPos.y : cursorPos.x);
if (oppositeEdge.monitorIndex >= 0)
{
outEdgeType = candidate;
return true;
}
}
return false;
}
POINT MonitorTopology::GetWrapDestination(HMONITOR fromMonitor, const POINT& cursorPos, EdgeType edgeType) const
{
// Get monitor index for edge map lookup
int monitorIndex = GetMonitorIndex(fromMonitor);
if (monitorIndex < 0)
{
return cursorPos; // Monitor not found
}
auto it = m_edgeMap.find({monitorIndex, edgeType});
if (it == m_edgeMap.end())
{
return cursorPos; // Edge not found
}
const MonitorEdge& fromEdge = it->second;
// Calculate relative position on current edge (0.0 to 1.0)
double relativePos = GetRelativePosition(fromEdge,
(edgeType == EdgeType::Left || edgeType == EdgeType::Right) ? cursorPos.y : cursorPos.x);
// Find opposite outer edge
MonitorEdge oppositeEdge = FindOppositeOuterEdge(edgeType,
(edgeType == EdgeType::Left || edgeType == EdgeType::Right) ? cursorPos.y : cursorPos.x);
if (oppositeEdge.monitorIndex < 0)
{
// No opposite edge found, wrap within same monitor
RECT monitorRect;
if (GetMonitorRect(fromMonitor, monitorRect))
{
POINT result = cursorPos;
switch (edgeType)
{
case EdgeType::Left:
result.x = monitorRect.right - 2;
break;
case EdgeType::Right:
result.x = monitorRect.left + 1;
break;
case EdgeType::Top:
result.y = monitorRect.bottom - 2;
break;
case EdgeType::Bottom:
result.y = monitorRect.top + 1;
break;
}
return result;
}
return cursorPos;
}
// Calculate target position on opposite edge
POINT result;
if (edgeType == EdgeType::Left || edgeType == EdgeType::Right)
{
// Horizontal edge -> vertical movement
result.x = oppositeEdge.position;
result.y = GetAbsolutePosition(oppositeEdge, relativePos);
}
else
{
// Vertical edge -> horizontal movement
result.y = oppositeEdge.position;
result.x = GetAbsolutePosition(oppositeEdge, relativePos);
}
return result;
}
MonitorEdge MonitorTopology::FindOppositeOuterEdge(EdgeType fromEdge, int relativePosition) const
{
EdgeType targetType;
bool findMax; // true = find max position, false = find min position
switch (fromEdge)
{
case EdgeType::Left:
targetType = EdgeType::Right;
findMax = true;
break;
case EdgeType::Right:
targetType = EdgeType::Left;
findMax = false;
break;
case EdgeType::Top:
targetType = EdgeType::Bottom;
findMax = true;
break;
case EdgeType::Bottom:
targetType = EdgeType::Top;
findMax = false;
break;
default:
return { .monitorIndex = -1 }; // Invalid edge type
}
MonitorEdge result = { .monitorIndex = -1 }; // -1 indicates not found
int extremePosition = findMax ? INT_MIN : INT_MAX;
for (const auto& edge : m_outerEdges)
{
if (edge.type != targetType)
{
continue;
}
// Check if this edge overlaps with the relative position
if (relativePosition >= edge.start && relativePosition <= edge.end)
{
if ((findMax && edge.position > extremePosition) ||
(!findMax && edge.position < extremePosition))
{
extremePosition = edge.position;
result = edge;
}
}
}
return result;
}
double MonitorTopology::GetRelativePosition(const MonitorEdge& edge, int coordinate) const
{
if (edge.end == edge.start)
{
return 0.5; // Avoid division by zero
}
int clamped = max(edge.start, min(coordinate, edge.end));
// Use int64_t to avoid overflow warning C26451
int64_t numerator = static_cast<int64_t>(clamped) - static_cast<int64_t>(edge.start);
int64_t denominator = static_cast<int64_t>(edge.end) - static_cast<int64_t>(edge.start);
return static_cast<double>(numerator) / static_cast<double>(denominator);
}
int MonitorTopology::GetAbsolutePosition(const MonitorEdge& edge, double relativePosition) const
{
// Use int64_t to prevent arithmetic overflow during subtraction and multiplication
int64_t range = static_cast<int64_t>(edge.end) - static_cast<int64_t>(edge.start);
int64_t offset = static_cast<int64_t>(relativePosition * static_cast<double>(range));
// Clamp result to int range before returning
int64_t result = static_cast<int64_t>(edge.start) + offset;
return static_cast<int>(result);
}
std::vector<MonitorTopology::GapInfo> MonitorTopology::DetectMonitorGaps() const
{
std::vector<GapInfo> gaps;
const int gapThreshold = 50; // Same as ADJACENCY_TOLERANCE
// Check each pair of monitors
for (size_t i = 0; i < m_monitors.size(); ++i)
{
for (size_t j = i + 1; j < m_monitors.size(); ++j)
{
const auto& m1 = m_monitors[i];
const auto& m2 = m_monitors[j];
// Check vertical overlap
int vOverlapStart = max(m1.rect.top, m2.rect.top);
int vOverlapEnd = min(m1.rect.bottom, m2.rect.bottom);
int vOverlap = vOverlapEnd - vOverlapStart;
if (vOverlap <= 0)
{
continue; // No vertical overlap, skip
}
// Check horizontal gap
int hGap = min(abs(m1.rect.right - m2.rect.left), abs(m2.rect.right - m1.rect.left));
if (hGap > gapThreshold)
{
GapInfo gap;
gap.monitor1Index = static_cast<int>(i);
gap.monitor2Index = static_cast<int>(j);
gap.horizontalGap = hGap;
gap.verticalOverlap = vOverlap;
gaps.push_back(gap);
}
}
}
return gaps;
}
HMONITOR MonitorTopology::GetMonitorFromPoint(const POINT& pt) const
{
return MonitorFromPoint(pt, MONITOR_DEFAULTTONEAREST);
}
bool MonitorTopology::GetMonitorRect(HMONITOR monitor, RECT& rect) const
{
// First try direct HMONITOR comparison
for (const auto& monitorInfo : m_monitors)
{
if (monitorInfo.hMonitor == monitor)
{
rect = monitorInfo.rect;
return true;
}
}
// Fallback: If direct comparison fails, try matching by current monitor info
MONITORINFO mi{};
mi.cbSize = sizeof(MONITORINFO);
if (GetMonitorInfo(monitor, &mi))
{
for (const auto& monitorInfo : m_monitors)
{
if (monitorInfo.rect.left == mi.rcMonitor.left &&
monitorInfo.rect.top == mi.rcMonitor.top &&
monitorInfo.rect.right == mi.rcMonitor.right &&
monitorInfo.rect.bottom == mi.rcMonitor.bottom)
{
rect = monitorInfo.rect;
return true;
}
}
}
return false;
}
HMONITOR MonitorTopology::GetMonitorFromRect(const RECT& rect) const
{
return MonitorFromRect(&rect, MONITOR_DEFAULTTONEAREST);
}
int MonitorTopology::GetMonitorIndex(HMONITOR monitor) const
{
// First try direct HMONITOR comparison (fast and accurate)
for (size_t i = 0; i < m_monitors.size(); ++i)
{
if (m_monitors[i].hMonitor == monitor)
{
return static_cast<int>(i);
}
}
// Fallback: If direct comparison fails (e.g., handle changed after display reconfiguration),
// try matching by position. Get the monitor's current rect and find matching stored rect.
MONITORINFO mi{};
mi.cbSize = sizeof(MONITORINFO);
if (GetMonitorInfo(monitor, &mi))
{
for (size_t i = 0; i < m_monitors.size(); ++i)
{
// Match by rect bounds
if (m_monitors[i].rect.left == mi.rcMonitor.left &&
m_monitors[i].rect.top == mi.rcMonitor.top &&
m_monitors[i].rect.right == mi.rcMonitor.right &&
m_monitors[i].rect.bottom == mi.rcMonitor.bottom)
{
Logger::trace(L"GetMonitorIndex: Found monitor {} via rect fallback (handle changed from {} to {})",
i, reinterpret_cast<uintptr_t>(m_monitors[i].hMonitor), reinterpret_cast<uintptr_t>(monitor));
return static_cast<int>(i);
}
}
// Log all stored monitors vs the requested one for debugging
Logger::warn(L"GetMonitorIndex: No match found. Requested monitor rect=({},{},{},{})",
mi.rcMonitor.left, mi.rcMonitor.top, mi.rcMonitor.right, mi.rcMonitor.bottom);
for (size_t i = 0; i < m_monitors.size(); ++i)
{
Logger::warn(L" Stored monitor {}: rect=({},{},{},{})",
i, m_monitors[i].rect.left, m_monitors[i].rect.top,
m_monitors[i].rect.right, m_monitors[i].rect.bottom);
}
}
else
{
Logger::warn(L"GetMonitorIndex: GetMonitorInfo failed for handle {}", reinterpret_cast<uintptr_t>(monitor));
}
return -1; // Not found
}

View File

@@ -0,0 +1,106 @@
// 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.
#pragma once
#include <windows.h>
#include <vector>
#include <map>
// Monitor information structure
struct MonitorInfo
{
HMONITOR hMonitor; // Direct handle for accurate lookup after display changes
RECT rect;
bool isPrimary;
int monitorId;
};
// Edge type enumeration
enum class EdgeType
{
Left = 0,
Right = 1,
Top = 2,
Bottom = 3
};
// Wrap mode enumeration (matches Settings UI dropdown)
enum class WrapMode
{
Both = 0, // Wrap in both directions
VerticalOnly = 1, // Only wrap top/bottom
HorizontalOnly = 2 // Only wrap left/right
};
// Represents a single edge of a monitor
struct MonitorEdge
{
int monitorIndex; // Index into m_monitors (stable across display changes)
EdgeType type;
int start; // For vertical edges: Y start; horizontal: X start
int end; // For vertical edges: Y end; horizontal: X end
int position; // For vertical edges: X coord; horizontal: Y coord
bool isOuter; // True if no adjacent monitor touches this edge
};
// Monitor topology helper - manages edge-based monitor layout
struct MonitorTopology
{
void Initialize(const std::vector<MonitorInfo>& monitors);
// Check if cursor is on an outer edge of the given monitor
// wrapMode filters which edges are considered (Both, VerticalOnly, HorizontalOnly)
bool IsOnOuterEdge(HMONITOR monitor, const POINT& cursorPos, EdgeType& outEdgeType, WrapMode wrapMode) const;
// Get the wrap destination point for a cursor on an outer edge
POINT GetWrapDestination(HMONITOR fromMonitor, const POINT& cursorPos, EdgeType edgeType) const;
// Get monitor at point (helper)
HMONITOR GetMonitorFromPoint(const POINT& pt) const;
// Get monitor rectangle (helper)
bool GetMonitorRect(HMONITOR monitor, RECT& rect) const;
// Get outer edges collection (for debugging)
const std::vector<MonitorEdge>& GetOuterEdges() const { return m_outerEdges; }
// Detect gaps between monitors that should be snapped together
struct GapInfo {
int monitor1Index;
int monitor2Index;
int horizontalGap;
int verticalOverlap;
};
std::vector<GapInfo> DetectMonitorGaps() const;
private:
std::vector<MonitorInfo> m_monitors;
std::vector<MonitorEdge> m_outerEdges;
// Map from (monitor index, edge type) to edge info
// Using monitor index instead of HMONITOR because HMONITOR handles can change
// when monitors are added/removed dynamically
std::map<std::pair<int, EdgeType>, MonitorEdge> m_edgeMap;
// Helper to resolve HMONITOR to monitor index at runtime
int GetMonitorIndex(HMONITOR monitor) const;
// Helper to get consistent HMONITOR from RECT
HMONITOR GetMonitorFromRect(const RECT& rect) const;
void BuildEdgeMap();
void IdentifyOuterEdges();
// Check if two edges are adjacent (within tolerance)
bool EdgesAreAdjacent(const MonitorEdge& edge1, const MonitorEdge& edge2, int tolerance = 50) const;
// Find the opposite outer edge for wrapping
MonitorEdge FindOppositeOuterEdge(EdgeType fromEdge, int relativePosition) const;
// Calculate relative position along an edge (0.0 to 1.0)
double GetRelativePosition(const MonitorEdge& edge, int coordinate) const;
// Convert relative position to absolute coordinate on target edge
int GetAbsolutePosition(const MonitorEdge& edge, double relativePosition) const;
};

File diff suppressed because it is too large Load Diff

View File

@@ -53,7 +53,7 @@ public partial class ContextMenuViewModel : ObservableObject,
{
if (SelectedItem is not null)
{
if (SelectedItem.MoreCommands.Count() > 1)
if (SelectedItem.PrimaryCommand is not null || SelectedItem.HasMoreCommands)
{
ContextMenuStack.Clear();
PushContextStack(SelectedItem.AllCommands);

View File

@@ -0,0 +1,26 @@
{
"solution": {
"path": "..\\..\\..\\PowerToys.slnx",
"projects": [
"src\\common\\Common.Search\\Common.Search.csproj",
"src\\common\\Common.UI\\Common.UI.csproj",
"src\\common\\ManagedCommon\\ManagedCommon.csproj",
"src\\common\\ManagedTelemetry\\Telemetry\\ManagedTelemetry.csproj",
"src\\common\\PowerToys.ModuleContracts\\PowerToys.ModuleContracts.csproj",
"src\\common\\SettingsAPI\\SettingsAPI.vcxproj",
"src\\common\\interop\\PowerToys.Interop.vcxproj",
"src\\common\\logger\\logger.vcxproj",
"src\\common\\version\\version.vcxproj",
"src\\logging\\logging.vcxproj",
"src\\modules\\MouseUtils\\MouseJump.Common\\MouseJump.Common.csproj",
"src\\modules\\Workspaces\\Workspaces.ModuleServices\\Workspaces.ModuleServices.csproj",
"src\\modules\\Workspaces\\WorkspacesCsharpLibrary\\WorkspacesCsharpLibrary.csproj",
"src\\modules\\ZoomIt\\ZoomItSettingsInterop\\ZoomItSettingsInterop.vcxproj",
"src\\modules\\awake\\Awake.ModuleServices\\Awake.ModuleServices.csproj",
"src\\modules\\cmdpal\\ext\\Microsoft.CmdPal.Ext.PowerToys\\Microsoft.CmdPal.Ext.PowerToys.csproj",
"src\\modules\\colorPicker\\ColorPicker.ModuleServices\\ColorPicker.ModuleServices.csproj",
"src\\modules\\fancyzones\\FancyZonesEditorCommon\\FancyZonesEditorCommon.csproj",
"src\\settings-ui\\Settings.UI.Library\\Settings.UI.Library.csproj"
]
}
}

View File

@@ -44,13 +44,14 @@ public sealed partial class CommandBar : UserControl,
public void Receive(OpenContextMenuMessage message)
{
if (!ViewModel.ShouldShowContextMenu)
{
return;
}
if (message.Element is null)
{
// This is invoked from the "More" button on the command bar
if (!ViewModel.ShouldShowContextMenu)
{
return;
}
_ = DispatcherQueue.TryEnqueue(
() =>
{
@@ -65,6 +66,7 @@ public sealed partial class CommandBar : UserControl,
}
else
{
// This is invoked from a specific element
_ = DispatcherQueue.TryEnqueue(
() =>
{

View File

@@ -17,12 +17,25 @@ public sealed partial class AppListItem : ListItem
{
private readonly AppCommand _appCommand;
private readonly AppItem _app;
private readonly Lazy<Details> _details;
private readonly Lazy<Task<IconInfo?>> _iconLoadTask;
private readonly Lazy<Task<Details>> _detailsLoadTask;
private InterlockedBoolean _isLoadingIcon;
private InterlockedBoolean _isLoadingDetails;
public override IDetails? Details { get => _details.Value; set => base.Details = value; }
public override IDetails? Details
{
get
{
if (_isLoadingDetails.Set())
{
_ = LoadDetailsAsync();
}
return base.Details;
}
set => base.Details = value;
}
public override IIconInfo? Icon
{
@@ -52,16 +65,22 @@ public sealed partial class AppListItem : ListItem
MoreCommands = AddPinCommands(_app.Commands!, isPinned);
_details = new Lazy<Details>(() =>
{
var t = BuildDetails();
t.Wait();
return t.Result;
});
_detailsLoadTask = new Lazy<Task<Details>>(BuildDetails);
_iconLoadTask = new Lazy<Task<IconInfo?>>(async () => await FetchIcon(useThumbnails));
}
private async Task LoadDetailsAsync()
{
try
{
Details = await _detailsLoadTask.Value;
}
catch (Exception ex)
{
Logger.LogWarning($"Failed to load details for {AppIdentifier}\n{ex}");
}
}
private async Task LoadIconAsync()
{
try
@@ -98,18 +117,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 +149,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 +193,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

@@ -4,6 +4,7 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Threading.Tasks;
using Microsoft.CmdPal.Ext.Apps.Properties;
using Microsoft.CmdPal.Ext.Apps.Utils;
@@ -33,6 +34,7 @@ internal sealed partial class RunAsAdminCommand : InvokableCommand
{
if (packaged)
{
// For UWP/packaged apps, use shell:AppsFolder which works from packaged context
var command = "shell:AppsFolder\\" + target;
command = Environment.ExpandEnvironmentVariables(command.Trim());
@@ -43,9 +45,37 @@ internal sealed partial class RunAsAdminCommand : InvokableCommand
}
else
{
var info = ShellCommand.GetProcessStartInfo(target, parentDir, string.Empty, ShellCommand.RunAsType.Administrator);
// For Win32 apps, use ActionRunner helper process to work around WinUI3/MSIX packaging limitation.
// When running from a packaged app, ShellExecute with "runas" verb may fail for certain apps
// (e.g., apps launched via .lnk shortcuts). ActionRunner runs outside the MSIX container,
// so it can properly invoke the UAC dialog.
var actionRunnerPath = ActionRunnerHelper.GetActionRunnerPath();
Process.Start(info);
if (string.IsNullOrEmpty(actionRunnerPath))
{
// Fallback to direct Process.Start if ActionRunner is not found
ExtensionHost.LogMessage($"ActionRunner not found, falling back to direct Process.Start for '{target}'");
var info = ShellCommand.GetProcessStartInfo(target, parentDir, string.Empty, ShellCommand.RunAsType.Administrator);
Process.Start(info);
return;
}
var args = $"-run-as-admin -target \"{target}\"";
if (!string.IsNullOrEmpty(parentDir))
{
args += $" -workingDir \"{parentDir}\"";
}
var processInfo = new ProcessStartInfo
{
FileName = actionRunnerPath,
Arguments = args,
UseShellExecute = false,
CreateNoWindow = true,
};
ExtensionHost.LogMessage($"Launching '{target}' as administrator via ActionRunner");
Process.Start(processInfo);
}
});
}

View File

@@ -4,6 +4,7 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Threading.Tasks;
using Microsoft.CmdPal.Ext.Apps.Properties;
using Microsoft.CmdPal.Ext.Apps.Utils;
@@ -29,9 +30,38 @@ internal sealed partial class RunAsUserCommand : InvokableCommand
{
await Task.Run(() =>
{
var info = ShellCommand.GetProcessStartInfo(target, parentDir, string.Empty, ShellCommand.RunAsType.OtherUser);
// Use ActionRunner helper process to work around WinUI3/MSIX packaging limitation.
// When running from a packaged app, ShellExecute with the "runas user" verb causes
// CredentialUIBroker.exe to spawn infinitely without showing the credential dialog.
// ActionRunner runs outside the MSIX container, so it can properly invoke the credential UI.
var actionRunnerPath = ActionRunnerHelper.GetActionRunnerPath();
Process.Start(info);
if (string.IsNullOrEmpty(actionRunnerPath))
{
// Fallback to direct Process.Start if ActionRunner is not found
// This may not work in packaged context, but provides a fallback for development
ExtensionHost.LogMessage($"ActionRunner not found, falling back to direct Process.Start for '{target}'");
var info = ShellCommand.GetProcessStartInfo(target, parentDir, string.Empty, ShellCommand.RunAsType.OtherUser);
Process.Start(info);
return;
}
var args = $"-run-as-user -target \"{target}\"";
if (!string.IsNullOrEmpty(parentDir))
{
args += $" -workingDir \"{parentDir}\"";
}
var processInfo = new ProcessStartInfo
{
FileName = actionRunnerPath,
Arguments = args,
UseShellExecute = false,
CreateNoWindow = true,
};
ExtensionHost.LogMessage($"Launching '{target}' as different user via ActionRunner");
Process.Start(processInfo);
});
}

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

@@ -0,0 +1,53 @@
// 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.IO;
using ManagedCommon;
namespace Microsoft.CmdPal.Ext.Apps.Utils;
/// <summary>
/// Helper class to locate and invoke the PowerToys ActionRunner executable.
/// ActionRunner is used to work around WinUI3/MSIX packaging limitations where
/// certain shell operations (like "Run as different user" or "Run as administrator")
/// don't work properly from within a packaged app context.
/// </summary>
internal static class ActionRunnerHelper
{
private const string ActionRunnerExeName = "PowerToys.ActionRunner.exe";
private static string? _cachedPath;
/// <summary>
/// Gets the path to the ActionRunner executable.
/// </summary>
/// <returns>The full path to ActionRunner.exe, or null if not found.</returns>
public static string? GetActionRunnerPath()
{
if (_cachedPath != null)
{
return _cachedPath;
}
_cachedPath = FindActionRunnerPath();
return _cachedPath;
}
private static string? FindActionRunnerPath()
{
// Use the standard PowerToys path resolver to find the installation directory.
// This handles registry lookups for installed versions and debug builds correctly.
var installPath = PowerToysPathResolver.GetPowerToysInstallPath();
if (!string.IsNullOrEmpty(installPath))
{
var actionRunnerPath = Path.Combine(installPath, ActionRunnerExeName);
if (File.Exists(actionRunnerPath))
{
return actionRunnerPath;
}
}
return null;
}
}

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

@@ -6,4 +6,6 @@
namespace cmdArg
{
const inline wchar_t* RUN_NONELEVATED = L"-run-non-elevated";
const inline wchar_t* RUN_AS_USER = L"-run-as-user";
const inline wchar_t* RUN_AS_ADMIN = L"-run-as-admin";
}

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

@@ -22,11 +22,15 @@ namespace Microsoft.PowerToys.Settings.UI.Library
[JsonPropertyName("disable_wrap_during_drag")]
public BoolProperty DisableWrapDuringDrag { get; set; }
[JsonPropertyName("wrap_mode")]
public IntProperty WrapMode { get; set; }
public CursorWrapProperties()
{
ActivationShortcut = DefaultActivationShortcut;
AutoActivate = new BoolProperty(false);
DisableWrapDuringDrag = new BoolProperty(true);
WrapMode = new IntProperty(0); // 0=Both (default), 1=VerticalOnly, 2=HorizontalOnly
}
}
}

View File

@@ -47,7 +47,16 @@ namespace Microsoft.PowerToys.Settings.UI.Library
// This can be utilized in the future if the settings.json file is to be modified/deleted.
public bool UpgradeSettingsConfiguration()
{
return false;
bool settingsUpgraded = false;
// Add WrapMode property if it doesn't exist (for users upgrading from older versions)
if (Properties.WrapMode == null)
{
Properties.WrapMode = new IntProperty(0); // Default to Both
settingsUpgraded = true;
}
return settingsUpgraded;
}
}
}

View File

@@ -47,6 +47,13 @@
<tkcontrols:SettingsCard ContentAlignment="Left" IsEnabled="{x:Bind ViewModel.IsCursorWrapEnabled, Mode=OneWay}">
<CheckBox x:Uid="MouseUtils_CursorWrap_DisableWrapDuringDrag" IsChecked="{x:Bind ViewModel.CursorWrapDisableWrapDuringDrag, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard Name="MouseUtilsCursorWrapWrapMode" x:Uid="MouseUtils_CursorWrap_WrapMode">
<ComboBox MinWidth="{StaticResource SettingActionControlMinWidth}" SelectedIndex="{x:Bind Path=ViewModel.CursorWrapWrapMode, Mode=TwoWay}">
<ComboBoxItem x:Uid="MouseUtils_CursorWrap_WrapMode_Both" />
<ComboBoxItem x:Uid="MouseUtils_CursorWrap_WrapMode_VerticalOnly" />
<ComboBoxItem x:Uid="MouseUtils_CursorWrap_WrapMode_HorizontalOnly" />
</ComboBox>
</tkcontrols:SettingsCard>
</tkcontrols:SettingsExpander.Items>
</tkcontrols:SettingsExpander>
</controls:SettingsGroup>

View File

@@ -2728,6 +2728,18 @@ From there, simply click on one of the supported files in the File Explorer and
<data name="MouseUtils_CursorWrap_AutoActivate.Content" xml:space="preserve">
<value>Automatically activate on utility startup</value>
</data>
<data name="MouseUtils_CursorWrap_WrapMode.Header" xml:space="preserve">
<value>Wrap mode</value>
</data>
<data name="MouseUtils_CursorWrap_WrapMode_VerticalOnly.Content" xml:space="preserve">
<value>Vertical only</value>
</data>
<data name="MouseUtils_CursorWrap_WrapMode_HorizontalOnly.Content" xml:space="preserve">
<value>Horizontal only</value>
</data>
<data name="MouseUtils_CursorWrap_WrapMode_Both.Content" xml:space="preserve">
<value>Vertical and horizontal</value>
</data>
<data name="Oobe_MouseUtils_MousePointerCrosshairs.Text" xml:space="preserve">
<value>Mouse Pointer Crosshairs</value>
<comment>Mouse as in the hardware peripheral.</comment>

View File

@@ -76,16 +76,24 @@ 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();
@@ -93,7 +101,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
SendConfigMSG = ipcMSGCallBackFunc;
_additionalActions = _advancedPasteSettings.Properties.AdditionalActions;
_customActions = _advancedPasteSettings.Properties.CustomActions.Value;
_customActions = _advancedPasteSettings.Properties.CustomActions.Value ?? new ObservableCollection<AdvancedPasteCustomAction>();
SetupSettingsFileWatcher();
@@ -469,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

@@ -113,6 +113,9 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
// Null-safe access in case property wasn't upgraded yet - default to TRUE
_cursorWrapDisableWrapDuringDrag = CursorWrapSettingsConfig.Properties.DisableWrapDuringDrag?.Value ?? true;
// Null-safe access in case property wasn't upgraded yet - default to 0 (Both)
_cursorWrapWrapMode = CursorWrapSettingsConfig.Properties.WrapMode?.Value ?? 0;
int isEnabled = 0;
Utilities.NativeMethods.SystemParametersInfo(Utilities.NativeMethods.SPI_GETCLIENTAREAANIMATION, 0, ref isEnabled, 0);
@@ -1083,6 +1086,34 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
}
}
public int CursorWrapWrapMode
{
get
{
return _cursorWrapWrapMode;
}
set
{
if (value != _cursorWrapWrapMode)
{
_cursorWrapWrapMode = value;
// Ensure the property exists before setting value
if (CursorWrapSettingsConfig.Properties.WrapMode == null)
{
CursorWrapSettingsConfig.Properties.WrapMode = new IntProperty(value);
}
else
{
CursorWrapSettingsConfig.Properties.WrapMode.Value = value;
}
NotifyCursorWrapPropertyChanged();
}
}
}
public void NotifyCursorWrapPropertyChanged([CallerMemberName] string propertyName = null)
{
OnPropertyChanged(propertyName);
@@ -1154,5 +1185,6 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
private bool _isCursorWrapEnabled;
private bool _cursorWrapAutoActivate;
private bool _cursorWrapDisableWrapDuringDrag; // Will be initialized in constructor from settings
private int _cursorWrapWrapMode; // 0=Both, 1=VerticalOnly, 2=HorizontalOnly
}
}