Compare commits

...

30 Commits

Author SHA1 Message Date
leileizhang
3c2cb4516a [ImageResizer] Temporarily disable AI Super Resolution feature (#44768)
<!-- 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
Temporarily disables the AI Super Resolution feature in Image Resizer
while keeping all code intact for re-enabling in a future release.

<!-- 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-17 11:22:21 +08:00
Gleb Khmyznikov
53c5e66cce [UI Tests][Light Switch] Fix tests: LS, Hosts, Worspaces, Mouse (#44754)
This pull request refactors the LightSwitch module to introduce a new
shared static library, `LightSwitchLib`, which centralizes theme
management logic and enables code sharing between the service and module
interface. The changes move theme-related code from the service and
module interface into this new library, update project references, and
clean up now-redundant files and includes.

**LightSwitchLib introduction and code deduplication:**

- Added a new static library project, `LightSwitchLib`, containing all
theme management logic (`SetSystemTheme`, `SetAppsTheme`,
`GetCurrentSystemTheme`, `GetCurrentAppsTheme`, `IsNightLightEnabled`)
and related files (`ThemeHelper.cpp`, `ThemeHelper.h`, `pch.h`,
`pch.cpp`). This code was previously duplicated in both the service and
module interface.
(`src/modules/LightSwitch/LightSwitchLib/LightSwitchLib.vcxproj`
[[1]](diffhunk://#diff-c38e95060ad294c9ed5c2bb769616bb52032a4330af7e268ad63d81a99dc1cadR1-R123)
`LightSwitchLib.vcxproj.filters`
[[2]](diffhunk://#diff-fcfc49f1628c274cd9a40aca385e03a1937f9e42958298f36155ad16a267ba9aR1-R33)
`ThemeHelper.cpp`
[[3]](diffhunk://#diff-f5ab83c022406172501172ee88e21294c7aba2a87fb30334d7c4d4fc9d736a56L1-R3)
`ThemeHelper.h`
[[4]](diffhunk://#diff-6609a7fc7abc61d4d0029f0fb605a9f4732511642af6e12851e86c234108169aR1-R10)
`pch.h`
[[5]](diffhunk://#diff-57e4d6ddad1d356a24555bce4d6cbb0d6a93386515254abf95573324454c94c2R1-R5)
`pch.cpp`
[[6]](diffhunk://#diff-87fbf215a559e7833ec06ff32aa7f8109fdf86d92b360fe44302fc16f1784d52R1)

- Updated the solution file and relevant project files to add and
reference `LightSwitchLib` from both `LightSwitchService` and
`LightSwitchModuleInterface`, ensuring both components use the shared
implementation. (`PowerToys.slnx`
[[1]](diffhunk://#diff-40c552fef4118125c3ccd6b156db518acec74b11150b193b31f18a2cc17a531eR668)
`LightSwitchService.vcxproj`
[[2]](diffhunk://#diff-51f54bd015aa96b38ddf4e96134ea542fac4b648566a23c2c86fe91a2b5a6bdaR58)
[[3]](diffhunk://#diff-51f54bd015aa96b38ddf4e96134ea542fac4b648566a23c2c86fe91a2b5a6bdaR113-R115)
`LightSwitchModuleInterface.vcxproj`
[[4]](diffhunk://#diff-72e859ee44b3f0087018e55708e850fb5040c5b8f72449d1cac30e8efb28e2c2R205-R207)
[[5]](diffhunk://#diff-72e859ee44b3f0087018e55708e850fb5040c5b8f72449d1cac30e8efb28e2c2L169-R179)

**Cleanup and removal of redundant code:**

- Removed old theme management code and headers from
`LightSwitchService` and `LightSwitchModuleInterface` now that logic
resides in `LightSwitchLib`. (`ThemeHelper.cpp`
[[1]](diffhunk://#diff-3e2766504c1cf989390508c613b2177cd5de14fb9de46df3b416f95f955338bfL1-L106)
`ThemeHelper.h`
[[2]](diffhunk://#diff-0e8540cace398ec3eebca416ca38d81262b689eca76a004584e686a605b7a242L1-L5)
`LightSwitchService.vcxproj`
[[3]](diffhunk://#diff-51f54bd015aa96b38ddf4e96134ea542fac4b648566a23c2c86fe91a2b5a6bdaL81)
[[4]](diffhunk://#diff-51f54bd015aa96b38ddf4e96134ea542fac4b648566a23c2c86fe91a2b5a6bdaL96)
`LightSwitchModuleInterface.vcxproj`
[[5]](diffhunk://#diff-72e859ee44b3f0087018e55708e850fb5040c5b8f72449d1cac30e8efb28e2c2L190)

- Removed duplicated registry path constants from `SettingsConstants.h`,
as they are now defined in the shared header. (`SettingsConstants.h`
[src/modules/LightSwitch/LightSwitchService/SettingsConstants.hL15-L17](diffhunk://#diff-e74db005ffb8b881a08c4dae1c1ead9dc732928a69cafb4c9e0bae8b86d4e24aL15-L17))

**Module interface improvements:**

- Added `ExportedFunctions.cpp` to the module interface, exposing theme
management functions as exports and using the shared library
implementation. (`ExportedFunctions.cpp`
[[1]](diffhunk://#diff-48acf3b77a8b6ac6fd1129afe1a677b34447ce39454e86ea04f1a1181a23b546R1-R22)
`LightSwitchModuleInterface.vcxproj`
[[2]](diffhunk://#diff-72e859ee44b3f0087018e55708e850fb5040c5b8f72449d1cac30e8efb28e2c2L169-R179)

**Minor test and logging adjustments:**

- Fixed a UI test to use the correct toggle name for the Hosts File
Editor. (`HostsSettingTests.cs`
[src/modules/Hosts/Hosts.UITests/HostsSettingTests.csL116-R116](diffhunk://#diff-3782109c99cd66a2c1b870a83d1f9d9807422479c89e03799b311ef5f13a2098L116-R116))
- Updated a log message to refer to `LightSwitchLib` instead of
`LightSwitchService` for clarity. (`ThemeHelper.cpp`
[src/modules/LightSwitch/LightSwitchLib/ThemeHelper.cppL66-R63](diffhunk://#diff-f5ab83c022406172501172ee88e21294c7aba2a87fb30334d7c4d4fc9d736a56L66-R63))
2026-01-16 13:58:51 -08:00
moooyo
4cde968c9b chore(imageresizer): fix log folder path formatting in App.xaml.cs (#44761)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request

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

- [ ] Closes: #xxx
<!-- - [ ] 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-16 17:10:41 +08:00
Kai Tao
e148a89288 Powertoys Extension: Trigger latest layout/monitor refresh for fancyzone in cmdpal extension (#44756)
<!-- 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
Best effort trigger refresh for the monitor/layout refresh for
fancyzones, user may experience error for first time run but will get
correct result in next visit.

This bring better experience than the always-stale-state, while still
remain performant command.

<!-- 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
Validated locally that second time visit will bring correct monitor when
plug/unplug monitor
2026-01-16 10:40:32 +08:00
Niels Laute
1dddf9fa2c "What's new" improvements (#44638)
<!-- 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

TO DO: Upgrade to the latest version of MarkdownTextBlock:
https://github.com/CommunityToolkit/Labs-Windows/pull/771

This PR introduces the following changes:
**Removed the custom titlebars on the OOBE Window, and replaced it with
the inbox WinUI `TitleBar` control.**

**New "What's new" experience following the VS Code release notes
experience**
- Created a new SCOOBE Windows that is a standalone window to better
visualize release notes.
- Adding a nav menu on the left to easily switch between release notes,
instead of a long page.
- Point releases are combined with the latest main release.. e.g. 0.96.1
is rendered above 0.96.0.
- The 'hero image' on main release notes will automatically be rendered
at the top of the page.
- Improved markdown styling for better readability.
- Pull requests links can now be clicked.
- Upgraded `CommunityToolkit.Labs.MarkdownTextblock` to the latest
version as it includes much needed bugfixes.

<img width="1234" height="819" alt="image"
src="https://github.com/user-attachments/assets/447b3136-306b-4f24-bc7a-c022a99e8e51"
/>

Note: the blurry image shown above will be replaced in new releases by
an image that fits the right dimensions.

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

---------

Co-authored-by: Leilei Zhang <leilzh@microsoft.com>
2026-01-15 16:37:08 +08:00
Tokenicrat 词元
0d59b9f790 Chore: Fix broken links in README.md (#44658)
The repository README.md has several formatting issues in [Installation
section](https://github.com/microsoft/PowerToys#-installation),
resulting in invalid link references. It's mainly because Markdown
references won't be rendered in HTML blocks.

This trivial PR just fixes that, nothing fancy. Have a nice day!

---------

Co-authored-by: Niels Laute <niels.laute@live.nl>
2026-01-15 09:21:12 +01:00
Shawn Yuan
e314485e85 Improve module enable/disable IPC and sorting reliability (#44734)
<!-- 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

- Refactored the runner logic for handling individual module
enable/disable updates. Instead of receiving the entire settings.json
via IPC, it now processes only single-module state updates, which avoids
race conditions and fixes a bug where modules could end up being
skipped.
- Fixed an issue where the sort order option could be deselected — it is
now enforced as a mutually exclusive choice.
- Fixed a potential race condition when updating the AppList control’s
sorting.


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

- [x] Closes: #44697
<!-- - [ ] 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

<!-- 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: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-15 15:29:46 +08:00
moooyo
f48c4a9a6f fix(common): correct minor version check for Windows 10 detection (#44716)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request

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

- [ ] Closes: #xxx
<!-- - [ ] 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-14 15:56:44 +08:00
moooyo
175403d86d fix: Improve tray icon theming and installer icon handling (#44715)
Add "svgs" directory and icon files to installer, ensuring proper
install/uninstall and registry registration. Enhance logging in
general_settings.cpp for theme-adaptive tray icon config changes.
Refactor tray_icon.cpp to improve icon loading robustness, add detailed
diagnostics, and ensure fallback to default icon if theme-adaptive icon
fails to load. These changes improve error handling and maintainability
for tray icon theming.

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

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

- [ ] Closes: #xxx
<!-- - [ ] 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-14 14:03:32 +08:00
moooyo
f7c57b05d7 fix(cmdpal): update copyright year in resources and test assertions (#44714)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request

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

- [ ] Closes: #xxx
<!-- - [ ] 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-14 11:29:15 +08:00
Michael Jolley
5098809e14 Devaluing fallback ranks in global sort (#44691)
Fixes #44630

Devaluing fallback ranks by 50% so direct matches are ranked higher.

---------

Co-authored-by: Jiří Polášek <me@jiripolasek.com>
2026-01-14 09:52:38 +08:00
Jiří Polášek
c8da70d6fa CmdPal: Replace assembly metadata attributes with preprocessor directives (#44707)
## Summary of the Pull Request

This PR replaces custom metadata attributes used to pass build-time
information with preprocessor directives. It appears that metadata can
be stripped from the final build output, even though it survived AOT in
earlier tests on the main and stable branches.

New preprocesor directives in Microsoft.CmdPal.UI:
- `BUILD_INFO_PUBLISH_AOT` - when `PublishAot` MSBuild parameter is
`true`
- `BUILD_INFO_PUBLISH_TRIMMED `- when `PublishTrimmed` MSBuild parameter
is `true`
- `BUILD_INFO_CIBUILD `- when `CIBuild` MSBuild parameter is `true`

Using preprocessor directives avoids this uncertainty and provides a
more reliable solution.

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

- [x] Closes: #44641
<!-- - [ ] 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-14 09:49:51 +08:00
leileizhang
22ce3b81ec Revert "Add Drag and Drop For Environment Variables (#40105)" (#44705)
This reverts commit 1b6b446915.

<!-- 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
Reverting this PR because WinUI 3 doesn’t support drag operations when
running as admin.

<!-- 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-13 18:52:30 +08:00
leileizhang
74448355f9 Peek: Show error message when activated in unsupported virtual folders (#44703)
<!-- 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

When Peek is activated in virtual folders like Home or Recent, it now
displays a clear error message instead of showing a stuck loading state
or "Search in Microsoft Store" link.

### Problem
When users activate Peek (press the hotkey) in Windows virtual folders
such as Home or Recent, the Shell API (SVGIO_SELECTION) returns 0 items
because these folders don't support the standard file selection
retrieval mechanism. Previously, this caused:

- The Peek window to appear stuck in a loading state
- The "Search in Microsoft Store" fallback UI to display incorrectly
- Subsequent Peek activations to fail until the window was manually
closed
<img width="2550" height="1310" alt="image"
src="https://github.com/user-attachments/assets/fd657e46-97f8-4042-bf43-971055f74700"
/>

### Solution

- Added a check in `Initialize()` to detect when no files are found
after querying the Shell
- Display an error InfoBar with a user-friendly message: "No files
selected or this folder is not supported for preview."
- Hide the `FilePreview` control when the error is shown to prevent
displaying irrelevant fallback UI
- Close the window automatically when the user dismisses the InfoBar
(clicks X)

<img width="1790" height="1193" alt="image"
src="https://github.com/user-attachments/assets/4a5c9bfa-1996-487a-86d3-5458431b14cb"
/>

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

- [x] Closes: #44576
<!-- - [ ] 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-13 18:04:28 +08:00
Kai Tao
031e365f57 2 fixes in 97 (#44704)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request
1. Should stop new executable when uninstall
2. Fix a workspaces editor deserialization issue


<!-- 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
It's a regression introduced when implementing the cmdpal workspaces
extension
Before fix:
<img width="235" height="135" alt="image"
src="https://github.com/user-attachments/assets/74bbf3ba-72cb-4a43-bba6-8eb96423f045"
/>

After fix:
<img width="341" height="294" alt="image"
src="https://github.com/user-attachments/assets/ce2236b8-c762-4bbf-85af-6a83a3987492"
/>
2026-01-13 17:18:48 +08:00
Shawn Yuan
569b4eed62 Fixed Quick Access sorting does not update after module status updated. (#44699)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request
This pull request introduces a small but important improvement to the
user experience in the `AllAppsViewModel`. Now, when a module's enabled
status is changed in the UI, the flyout menu items are immediately
re-sorted to reflect the new state.

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

- [x] Closes: #44634 
<!-- - [ ] 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-13 16:29:06 +08:00
leileizhang
b68b84532c Fix ESRP Code Signing Node 16 EOL is blocking app signing (#44695)
<!-- 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’re hitting an issue where ESRP Code Signing (EsrpCodeSigning@5) is
now blocking our app from being signed.
The pipeline reports a Node 16 EOL dependency, and at this point the app
can no longer be signed successfully.

Fix Upgrade the version to 6 

<!-- 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-13 12:36:23 +08:00
leileizhang
8b79da5d49 Fix Image Resizer doesn’t work when using the UI. (#44687)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request
1. Update the CLI Log Folder
2. Fixes an `ArgumentOutOfRangeException` that occurs in ImageResizer
when the `CustomSize` or `AiSize` properties change.
<img width="1494" height="530" alt="image"
src="https://github.com/user-attachments/assets/3d8dbe8c-4d21-46bc-9c10-f6d26161787f"
/>

Changed the collection changed notification from `Replace` to `Reset`
for both `CustomSize` and `AiSize` property changes. While `Reset` is
less efficient (it forces a full UI refresh rather than updating a
single item), it avoids the index validation issue since it doesn't
require specifying an index.

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

- [x] Closes: #44686
<!-- - [ ] 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-13 12:36:14 +08:00
Gordon Lam
ab28777514 build(packages): pin SixLabors.ImageSharp version to restore functionality and up-to-date patches (#44694)
## Summary of the Pull Request
Pin the `SixLabors.ImageSharp` package version to `2.1.12` to restore
functionality and apply necessary patches.

## PR Checklist
- [ ] Closes: #xxx
- [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

## Detailed Description of the Pull Request / Additional comments
Pinning the `SixLabors.ImageSharp` version addresses compatibility
issues with the `CoenM.ImageSharp.ImageHash` package.

## Validation Steps Performed
Manual validation confirmed that functionality is restored with the
pinned version.
```
2026-01-13 11:02:20 +08:00
Kai Tao
febaec0741 Add icon for powertoys extension and quickaccess exe (#44674)
<!-- 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, inject powertoys icon to these two exe

<!-- 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
Add two self contained icon to the two process:
<img width="678" height="382" alt="image"
src="https://github.com/user-attachments/assets/f682ab93-75ed-4292-ba3a-78b65795d3cc"
/>
2026-01-13 10:55:08 +08:00
leileizhang
c88fe1fa0e [FancyZonesCLI] Fix PowerShell GUID parsing and add subcommand help (#44676)
<!-- 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
This PR improves FancyZones CLI usability with three enhancements:

1. **PowerShell GUID compatibility**: Users can now pass GUIDs without
braces (e.g., `0CEBCBA9-9C32-4395-B93E-DC77485AD6D0`), avoiding
PowerShell's script block interpretation issue
2. **Subcommand help**: Added `--help` support for individual commands
(e.g., `FancyZonesCLI set-layout --help`)
3. **Friendly error messages**: When PowerShell incorrectly interprets
`{GUID}` as a script block, displays a helpful message instead of
cryptic errors

<img width="1313" height="476" alt="image"
src="https://github.com/user-attachments/assets/65eb403a-05ec-412b-852d-b20386792b34"
/>
<img width="1311" height="598" alt="image"
src="https://github.com/user-attachments/assets/a07a304e-a353-43c5-9e88-84840ffd86f2"
/>

<img width="1309" height="380" alt="image"
src="https://github.com/user-attachments/assets/3c1a071d-325d-486a-bfa7-f4b974523400"
/>

- [x] Closes: #44633 #44675
<!-- - [ ] 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-12 16:40:22 +08:00
Kai Tao
fd88fa18d4 Fancyzones: Fix a custom layout not work in fancyzone and powertoys extension (#44661)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request
1. Fix a issue that fancyzone custom layouts be able to work
2. Fix monitor info build and the icon render

<!-- 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
<img width="1172" height="701" alt="image"
src="https://github.com/user-attachments/assets/0cfa71d9-8ce2-4d27-8995-c797f40f927f"
/>
2026-01-12 09:23:40 +08:00
Jaylyn Barbee
a6b8cea7cd GPO configured correctly and tested (#44567)
Ensure that Light Switch respects GPO configuration. 
In "Not configured" --> No message, everything is under the user's
control
In "Enabled" --> Module is forced enabled, policy message shows, able to
change settings
In "Disabled" --> Module is forced disabled, policy message shows,
unable to change settings
2026-01-09 10:29:17 -05:00
Jaylyn Barbee
5f61057b38 Adding a quick access button for Light Switch (#44640)
Adds a button for Light Switch in the Quick Access section of the
Dashboard page. Clicking the button will toggle the theme.
<img width="1886" height="1173" alt="image"
src="https://github.com/user-attachments/assets/7923e1ac-aeea-47ab-b648-2400cb6f3ca4"
/>
2026-01-09 10:28:54 -05:00
Shawn Yuan
0314a709f5 Optimize the module list logic (#44628)
<!-- 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 refactors how module enable/disable state changes are
handled in the settings UI, centralizing and simplifying the logic for
updating and notifying these changes. The main improvements include
unifying the callback mechanism for enable state changes, reducing code
duplication, and making the update process more robust against circular
updates.

**Refactoring and Simplification of Enable State Handling:**

* Introduced a unified `EnabledChangedCallback` and `UpdateStatus`
method in the base `ModuleListItem` class to handle enable/disable state
changes and notifications, replacing redundant logic in derived classes
like `FlyoutMenuItem` and `DashboardListItem`.
[[1]](diffhunk://#diff-23ab2cc13a1098a6071b3e12ce0919b7eba451d7683f6f62e5ec2cf661778a4cR21-R36)
[[2]](diffhunk://#diff-23ab2cc13a1098a6071b3e12ce0919b7eba451d7683f6f62e5ec2cf661778a4cR99-R103)
[[3]](diffhunk://#diff-5033dabc0e3ec7d01509b9d58878b9ee5745378d5e3a7fa92779bf9c111bcffcL25-L39)
[[4]](diffhunk://#diff-9c93f68ee87a48d8affd140224601da95d7fe9642ad24350c7527d0f5773ec7dL29-L43)
* Updated all usages to call `UpdateStatus` instead of directly setting
`IsEnabled`, ensuring callbacks are managed consistently and preventing
unnecessary notifications during programmatic updates.
[[1]](diffhunk://#diff-734ba1b4b3044eb540bba08334bd141c968a113625be2d92c831f3cc3debc62fL108-R109)
[[2]](diffhunk://#diff-aea3404667e7a3de2750bf9ab7ee8ff5e717892caa68ee1de86713cf8e21b44cL261-R260)

**Improvements to Callback and Update Logic:**

* Changed the signature of UI event handlers to use the base
`ModuleListItem` type, improving code maintainability and reducing
casting and duplication.
[[1]](diffhunk://#diff-734ba1b4b3044eb540bba08334bd141c968a113625be2d92c831f3cc3debc62fL160-R161)
[[2]](diffhunk://#diff-aea3404667e7a3de2750bf9ab7ee8ff5e717892caa68ee1de86713cf8e21b44cL278-R287)
* Removed obsolete or redundant flags and logic for tracking update
state, further simplifying the codebase.
[[1]](diffhunk://#diff-aea3404667e7a3de2750bf9ab7ee8ff5e717892caa68ee1de86713cf8e21b44cL53)
[[2]](diffhunk://#diff-aea3404667e7a3de2750bf9ab7ee8ff5e717892caa68ee1de86713cf8e21b44cL328)
[[3]](diffhunk://#diff-aea3404667e7a3de2750bf9ab7ee8ff5e717892caa68ee1de86713cf8e21b44cL343-L346)

**Project and Namespace Adjustments:**

* Updated the project file to include all localized resource files
recursively, improving localization support.
* Added a missing namespace import to ensure proper type resolution.
* Added a missing `using System;` directive for the `Action` delegate.
<!-- 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-09 13:06:50 +08:00
Gordon Lam
a246789719 docs(prompts): sync commit/pr guidance and Copilot settings (#44627)
## Summary of the Pull Request
- Clarified the PR-summary prompt to prepend a PR title and to reuse the
Conventional Commit rules from
`.github/prompts/create-commit-title.prompt.md`.
- Expanded the commit-title prompt with clearer purpose, inputs, and
Conventional Commit guidance.
- Added workspace Copilot chat settings in `.vscode/settings.json` to
point review/commit/PR generation at the repo prompt files.

e.g. for commit tile generation:
<img width="562" height="376" alt="image"
src="https://github.com/user-attachments/assets/ca11d117-e4ad-4d1e-abb7-2b4600690f45"
/>


## PR Checklist
- [ ] Closes: N/A
- [ ] Communication: N/A (prompt/settings maintenance)
- [ ] Tests: Not run (prompt/settings-only change)

## Detailed Description of the Pull Request / Additional comments
- The PR-summary workflow now directs PR title generation to the
existing commit-title prompt instead of duplicating rules, and places
the title above the filled template.
- The commit-title prompt now spells out required diff command, decision
steps, and Conventional Commit examples.
- VS Code Copilot chat settings ensure review, commit, and PR
description generation use the repository prompts consistently.

## Validation Steps Performed
- Not run (no product code changes)
2026-01-09 11:38:05 +08:00
Shawn Yuan
af401dd6e9 Fix quickaccess localization issue (#44626)
<!-- 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 resource file inclusion pattern in the
`PowerToys.QuickAccess.csproj` project file to support localization for
multiple languages.

Localization improvements:

* Changed the `PRIResource` include path to recursively include all
`Resources.resw` files from subdirectories, enabling support for
multiple language resource files instead of just `en-us`.
<!-- 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
- [x] **Localization:** All end-user-facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx

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

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2026-01-09 10:27:30 +08:00
MemphiZ
6c2a99dfd6 Fix FancyZones editor overlay on mixed-DPI multi-monitor setups (#44440)
## Summary of the Pull Request

Use DPI-unaware context when positioning overlay windows to match the
coordinate space from the C++ backend.

The FancyZones editor overlay windows were incorrectly positioned on
secondary monitors when using different DPI scaling (e.g.,
125%/150%/125%). Zones appeared shifted or clipped because they extended
past monitor edges.

## PR Checklist

- [x] Closes: #43363
- [x] Closes: #43386
- [ ] **Communication:** I've discussed this with core contributors
already.
- [ ] **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

## Detailed Description of the Pull Request / Additional comments

### Root Cause
The C++ backend uses a DPI-unaware thread to get virtual screen
coordinates, but the WPF editor (PerMonitorV2 DPI-aware) interpreted
these coordinates with DPI scaling applied, causing misalignment on
non-primary monitors.

### Fix
- **EditorParameters.cpp**: Use consistent virtual coordinates for all
monitor properties (removed `DPIAware::Convert` that was only applied to
dimensions)
- **Monitor.cs**: Reposition overlay windows using DPI-unaware context
after HWND creation, matching the coordinate space from C++ backend
- **NativeMethods.cs**: Added `SetWindowPositionDpiUnaware()` using
`SetThreadDpiAwarenessContext` to temporarily switch DPI awareness

## Validation Steps Performed

Manually tested on 3-monitor setup with 125%/150%/125% DPI scaling -
overlays now correctly cover each monitor's work area.

---

🤖 *This fix was developed with [Claude Code](https://claude.ai/code)
after 6 hours of debugging DPI coordinate systems together.*

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Leilei Zhang <leilzh@microsoft.com>
2026-01-09 09:57:58 +08:00
Mike Griese
7cf32bf204 cmdpal: bump to 0.8 (#44622)
title
2026-01-09 09:55:27 +08:00
Jiří Polášek
ae9ba62a40 CmdPal: Remove subtitle from all built-in top level commands (#44621)
## Summary of the Pull Request

This PR removes subtitles from all built-in top-level commands, except
for fallbacks, apps, and bookmarks.

<img width="885" height="987" alt="image"
src="https://github.com/user-attachments/assets/4da7377d-2c86-4658-a96b-33a881333639"
/>

<!-- 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-09 09:42:06 +08:00
117 changed files with 2419 additions and 1293 deletions

View File

@@ -209,6 +209,7 @@ changecursor
CHILDACTIVATE
CHILDWINDOW
CHOOSEFONT
CIBUILD
cidl
CIELCh
cim

View File

@@ -6,13 +6,45 @@ description: 'Generate an 80-character git commit title for the local diff'
# Generate Commit Title
**Goal:** Provide a ready-to-paste git commit title (<= 80 characters) that captures the most important local changes since `HEAD`.
## Purpose
Provide a single-line, ready-to-paste git commit title (<= 80 characters) that reflects the most important local changes since `HEAD`.
**Workflow:**
1. Run a single command to view the local diff since the last commit:
```@terminal
git diff HEAD
```
2. From that diff, identify the dominant area (reference key paths like `src/modules/*`, `doc/devdocs/**`, etc.), the type of change (bug fix, docs update, config tweak), and any notable impact.
3. Draft a concise, imperative commit title summarizing the dominant change. Keep it plain ASCII, <= 80 characters, and avoid trailing punctuation. Mention the primary component when obvious (for example `FancyZones:` or `Docs:`).
4. Respond with only the final commit title on a single line so it can be pasted directly into `git commit`.
## Input to collect
- Run exactly one command to view the local diff:
```@terminal
git diff HEAD
```
## How to decide the title
1. From the diff, find the dominant area (e.g., `src/modules/*`, `doc/devdocs/**`) and the change type (bug fix, docs update, config tweak).
2. Draft an imperative, plain-ASCII title that:
- Mentions the primary component when obvious (e.g., `FancyZones:` or `Docs:`)
- Stays within 80 characters and has no trailing punctuation
## Final output
- Reply with only the commit title on a single line—no extra text.
## PR title convention (when asked)
Use Conventional Commits style:
`<type>(<scope>): <summary>`
**Allowed types**
- feat, fix, docs, refactor, perf, test, build, ci, chore
**Scope rules**
- Use a short, PowerToys-focused scope (one word preferred). Common scopes:
- Core: `runner`, `settings-ui`, `common`, `docs`, `build`, `ci`, `installer`, `gpo`, `dsc`
- Modules: `fancyzones`, `powerrename`, `awake`, `colorpicker`, `imageresizer`, `keyboardmanager`, `mouseutils`, `peek`, `hosts`, `file-locksmith`, `screen-ruler`, `text-extractor`, `cropandlock`, `paste`, `powerlauncher`
- If unclear, pick the closest module or subsystem; omit only if unavoidable
**Summary rules**
- Imperative, present tense (“add”, “update”, “remove”, “fix”)
- Keep it <= 72 characters when possible; be specific, avoid “misc changes”
**Examples**
- `feat(fancyzones): add canvas template duplication`
- `fix(mouseutils): guard crosshair toggle when dpi info missing`
- `docs(runner): document tray icon states`
- `build(installer): align wix v5 suffix flag`
- `ci(ci): cache pipeline artifacts for x64`

View File

@@ -22,3 +22,4 @@ description: 'Generate a PowerToys-ready pull request description from the local
5. Confirm validation: list tests executed with results or state why tests were skipped in line with repo guidance.
6. Load `.github/pull_request_template.md`, mirror its section order, and populate it with the gathered facts. Include only relevant checklist entries, marking them `[x]/[ ]` and noting any intentional omissions as "N/A".
7. Present the filled template inside a fenced ```markdown code block with no extra commentary so it is ready to paste into a PR, clearly flagging any placeholders that still need user input.
8. Prepend the PR title above the filled template, applying the Conventional Commit type/scope rules from `.github/prompts/create-commit-title.prompt.md`; pick the dominant component from the diff and keep the title concise and imperative.

View File

@@ -10,8 +10,8 @@ description: 'Resolve Code scanning / check-spelling comments on the active PR'
**Guardrails:**
- Update only discussion threads authored by `github-actions` or `github-actions[bot]` that mention `Code scanning results / check-spelling`.
- Resolve findings solely by editing `.github/actions/spell-check/expect.txt`; reuse existing entries.
- Leave all other files and topics untouched.
- Prefer improving the wording in the originally flagged file when it clarifies intent without changing meaning; if the wording is already clear/standard for the context, handle it via `.github/actions/spell-check/expect.txt` and reuse existing entries.
- Limit edits to the flagged text and `.github/actions/spell-check/expect.txt`; leave all other files and topics untouched.
**Prerequisites:**
- Install GitHub CLI if it is not present: `winget install GitHub.cli`.
@@ -20,5 +20,6 @@ description: 'Resolve Code scanning / check-spelling comments on the active PR'
**Workflow:**
1. Determine the active pull request with a single `gh pr view --json number` call (default to the current branch).
2. Fetch all PR discussion data once via `gh pr view --json comments,reviews` and filter to check-spelling comments authored by `github-actions` or `github-actions[bot]` that are not minimized; when several remain, process only the most recent comment body.
3. For each flagged token, review `.github/actions/spell-check/expect.txt` for an equivalent term (for example an existing lowercase variant); when found, reuse that normalized term rather than adding a new entry, even if the flagged token differs only by casing. Only add a new entry after confirming no equivalent already exists.
4. Add any remaining missing token to `.github/actions/spell-check/expect.txt`, keeping surrounding formatting intact.
3. For each flagged token, first consider tightening or rephrasing the original text to avoid the false positive while keeping the meaning intact; if the existing wording is already normal and professional for the context, proceed to allowlisting instead of changing it.
4. When allowlisting, review `.github/actions/spell-check/expect.txt` for an equivalent term (for example an existing lowercase variant); when found, reuse that normalized term rather than adding a new entry, even if the flagged token differs only by casing. Only add a new entry after confirming no equivalent already exists.
5. Add any remaining missing token to `.github/actions/spell-check/expect.txt`, keeping surrounding formatting intact.

View File

@@ -10,7 +10,7 @@ parameters:
default: {}
steps:
- task: EsrpCodeSigning@5
- task: EsrpCodeSigning@6
displayName: 🔏 ${{ parameters.displayName }}
inputs:
ConnectedServiceName: ${{ parameters.signingIdentity.serviceName }}

17
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,17 @@
{
"github.copilot.chat.reviewSelection.instructions": [
{
"file": ".github/prompts/review-pr.prompt.md"
}
],
"github.copilot.chat.commitMessageGeneration.instructions": [
{
"file": ".github/prompts/create-commit-title.prompt.md"
}
],
"github.copilot.chat.pullRequestDescriptionGeneration.instructions": [
{
"file": ".github/prompts/create-pr-summary.prompt.md"
}
]
}

View File

@@ -13,6 +13,8 @@
<PackageVersion Include="Microsoft.Bot.AdaptiveExpressions.Core" Version="4.23.0" />
<PackageVersion Include="Appium.WebDriver" Version="4.4.5" />
<PackageVersion Include="CoenM.ImageSharp.ImageHash" Version="1.3.6" />
<!-- Pin the SixLabors.ImageSharp version (a transitive dependency of CoenM.ImageSharp.ImageHash) to restore functionality and apply patches. -->
<PackageVersion Include="SixLabors.ImageSharp" Version="2.1.12" />
<PackageVersion Include="CommunityToolkit.Common" Version="8.4.0" />
<PackageVersion Include="CommunityToolkit.Mvvm" Version="8.4.0" />
<PackageVersion Include="CommunityToolkit.WinUI.Animations" Version="8.2.250402" />
@@ -24,7 +26,7 @@
<PackageVersion Include="CommunityToolkit.WinUI.Converters" Version="8.2.250402" />
<PackageVersion Include="CommunityToolkit.WinUI.Extensions" Version="8.2.250402" />
<PackageVersion Include="CommunityToolkit.WinUI.UI.Controls.DataGrid" Version="7.1.2" />
<PackageVersion Include="CommunityToolkit.Labs.WinUI.Controls.MarkdownTextBlock" Version="0.1.251002-build.2316" />
<PackageVersion Include="CommunityToolkit.Labs.WinUI.Controls.MarkdownTextBlock" Version="0.1.260107-build.2454" />
<PackageVersion Include="ControlzEx" Version="6.0.0" />
<PackageVersion Include="HelixToolkit" Version="2.24.0" />
<PackageVersion Include="HelixToolkit.Core.Wpf" Version="2.24.0" />

View File

@@ -665,6 +665,7 @@
</Project>
</Folder>
<Folder Name="/modules/LightSwitch/">
<Project Path="src/modules/LightSwitch/LightSwitchLib/LightSwitchLib.vcxproj" Id="79267138-2895-4346-9021-21408d65379f" />
<Project Path="src/modules/LightSwitch/LightSwitchModuleInterface/LightSwitchModuleInterface.vcxproj" Id="38177d56-6ad1-4adf-88c9-2843a7932166" />
<Project Path="src/modules/LightSwitch/LightSwitchService/LightSwitchService.vcxproj" Id="08e71c67-6a7e-4ca1-b04e-2fb336410bac" />
</Folder>

View File

@@ -48,7 +48,7 @@ But to get started quickly, choose one of the installation methods below:
<details open>
<summary><strong>Download .exe from GitHub</strong></summary>
<br/>
Go to the [PowerToys GitHub releases][github-release-link], click Assets to reveal the downloads, and choose the installer that matches your architecture and install scope. For most devices, that's the x64 per-user installer.
Go to the <a href="https://aka.ms/installPowerToys">PowerToys GitHub releases</a>, click Assets to reveal the downloads, and choose the installer that matches your architecture and install scope. For most devices, that's the x64 per-user installer.
<!-- items that need to be updated release to release -->
[github-next-release-work]: https://github.com/microsoft/PowerToys/issues?q=is%3Aissue+milestone%3A%22PowerToys+0.97%22
@@ -83,7 +83,7 @@ You can easily install PowerToys from the Microsoft Store:
<details>
<summary><strong>WinGet</strong></summary>
<br/>
Download PowerToys from [WinGet][winget-link]. Updating PowerToys via winget will respect the current PowerToys installation scope. To install PowerToys, run the following command from the command line / PowerShell:
Download PowerToys from <a href="https://github.com/microsoft/winget-cli#installing-the-client">WinGet</a>. Updating PowerToys via winget will respect the current PowerToys installation scope. To install PowerToys, run the following command from the command line / PowerShell:
*User scope installer [default]*
```powershell
@@ -99,7 +99,7 @@ winget install --scope machine Microsoft.PowerToys -s winget
<details>
<summary><strong>Other methods</strong></summary>
<br/>
There are [community driven install methods](./doc/unofficialInstallMethods.md) such as Chocolatey and Scoop. If these are your preferred install solutions, you can find the install instructions there.
There are <a href="https://learn.microsoft.com/windows/powertoys/install#community-driven-install-tools">community driven install methods</a> such as Chocolatey and Scoop. If these are your preferred install solutions, you can find the install instructions there.
</details>
## ✨ What's new

View File

@@ -1549,7 +1549,7 @@ UINT __stdcall TerminateProcessesCA(MSIHANDLE hInstall)
}
processes.resize(bytes / sizeof(processes[0]));
std::array<std::wstring_view, 42> processesToTerminate = {
std::array<std::wstring_view, 44> processesToTerminate = {
L"PowerToys.PowerLauncher.exe",
L"PowerToys.Settings.exe",
L"PowerToys.AdvancedPaste.exe",
@@ -1584,12 +1584,14 @@ UINT __stdcall TerminateProcessesCA(MSIHANDLE hInstall)
L"PowerToys.MouseWithoutBordersService.exe",
L"PowerToys.CropAndLock.exe",
L"PowerToys.EnvironmentVariables.exe",
L"PowerToys.QuickAccess.exe",
L"PowerToys.WorkspacesSnapshotTool.exe",
L"PowerToys.WorkspacesLauncher.exe",
L"PowerToys.WorkspacesLauncherUI.exe",
L"PowerToys.WorkspacesEditor.exe",
L"PowerToys.WorkspacesWindowArranger.exe",
L"Microsoft.CmdPal.UI.exe",
L"Microsoft.CmdPal.Ext.PowerToys.exe",
L"PowerToys.ZoomIt.exe",
L"PowerToys.exe",
};

View File

@@ -61,6 +61,16 @@
</RegistryKey>
<File Source="$(var.RepoDir)\Notice.md" Id="Notice.md" />
</Component>
<Directory Id="SvgsFolder" Name="svgs">
<Component Id="svgs_icons" Guid="A9B7C5D3-E1F2-4A6B-8C9D-0E1F2A3B4C5D" Bitness="always64">
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
<RegistryValue Type="string" Name="svgs_icons" Value="" KeyPath="yes" />
</RegistryKey>
<File Id="icon.ico" Source="$(var.BinDir)svgs\icon.ico" />
<File Id="PowerToysWhite.ico" Source="$(var.BinDir)svgs\PowerToysWhite.ico" />
<File Id="PowerToysDark.ico" Source="$(var.BinDir)svgs\PowerToysDark.ico" />
</Component>
</Directory>
</DirectoryRef>
<?if $(var.PerUser) = "true" ?>
@@ -112,6 +122,7 @@
<RemoveFolder Id="RemoveBaseApplicationsAssetsFolder" Directory="BaseApplicationsAssetsFolder" On="uninstall" />
<RemoveFolder Id="RemoveWinUI3AppsInstallFolder" Directory="WinUI3AppsInstallFolder" On="uninstall" />
<RemoveFolder Id="RemoveWinUI3AppsAssetsFolder" Directory="WinUI3AppsAssetsFolder" On="uninstall" />
<RemoveFolder Id="RemoveSvgsFolder" Directory="SvgsFolder" On="uninstall" />
<RemoveFolder Id="RemoveINSTALLFOLDER" Directory="INSTALLFOLDER" On="uninstall" />
</Component>
<ComponentRef Id="powertoys_exe" />
@@ -120,6 +131,7 @@
<ComponentRef Id="powertoys_toast_clsid" />
<ComponentRef Id="License_rtf" />
<ComponentRef Id="Notice_md" />
<ComponentRef Id="svgs_icons" />
<ComponentRef Id="DesktopShortcut" />
<?if $(var.PerUser) = "true" ?>
<ComponentRef Id="powertoys_env_path_user" />

View File

@@ -66,5 +66,10 @@ namespace PowerToys.GPOWrapperProjection
{
return (GpoRuleConfigured)PowerToys.GPOWrapper.GPOWrapper.GetConfiguredWorkspacesEnabledValue();
}
public static GpoRuleConfigured GetConfiguredLightSwitchEnabledValue()
{
return (GpoRuleConfigured)PowerToys.GPOWrapper.GPOWrapper.GetConfiguredLightSwitchEnabledValue();
}
}
}

View File

@@ -10,7 +10,7 @@ namespace ManagedCommon
{
public static bool IsWindows10()
{
return Environment.OSVersion.Version.Major >= 10 && Environment.OSVersion.Version.Minor < 22000;
return Environment.OSVersion.Version.Major >= 10 && Environment.OSVersion.Version.Build < 22000;
}
public static bool IsWindows11()

View File

@@ -466,39 +466,27 @@
TextChanged="EditVariableDialogValueTxtBox_TextChanged"
TextWrapping="Wrap" />
<MenuFlyoutSeparator Visibility="{Binding ShowAsList, Converter={StaticResource BoolToVisibilityConverter}}" />
<ListView
<ItemsControl
x:Name="EditVariableValuesList"
Margin="0,-8,0,12"
HorizontalAlignment="Stretch"
AllowDrop="True"
CanDragItems="True"
CanReorderItems="True"
DragItemsCompleted="EditVariableValuesList_DragItemsCompleted"
ItemsSource="{Binding ValuesList, Mode=TwoWay}"
Visibility="{Binding ShowAsList, Converter={StaticResource BoolToVisibilityConverter}}">
<ListView.ItemTemplate>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="32" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="40" />
</Grid.ColumnDefinitions>
<FontIcon
Grid.Column="0"
Margin="0,0,8,0"
FontSize="16"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Glyph="&#xE759;" />
<TextBox
Grid.Column="1"
Background="Transparent"
BorderBrush="Transparent"
LostFocus="EditVariableValuesListTextBox_LostFocus"
Text="{Binding Text}" />
<Button
x:Uid="More_Options_Button"
Grid.Column="2"
Grid.Column="1"
VerticalAlignment="Center"
Content="&#xE712;"
FontFamily="{ThemeResource SymbolThemeFontFamily}"
@@ -535,8 +523,8 @@
</Button>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</ScrollViewer>
</ContentDialog>

View File

@@ -16,8 +16,6 @@ namespace EnvironmentVariablesUILib
{
public sealed partial class EnvironmentVariablesMainPage : Page
{
private const string ValueListSeparator = ";";
private sealed class RelayCommandParameter
{
public RelayCommandParameter(Variable variable, VariablesSet set)
@@ -442,7 +440,7 @@ namespace EnvironmentVariablesUILib
variable.ValuesList.Move(index, index - 1);
}
var newValues = string.Join(ValueListSeparator, variable.ValuesList?.Select(x => x.Text).ToArray());
var newValues = string.Join(";", variable.ValuesList?.Select(x => x.Text).ToArray());
EditVariableDialogValueTxtBox.Text = newValues;
}
@@ -463,7 +461,7 @@ namespace EnvironmentVariablesUILib
variable.ValuesList.Move(index, index + 1);
}
var newValues = string.Join(ValueListSeparator, variable.ValuesList?.Select(x => x.Text).ToArray());
var newValues = string.Join(";", variable.ValuesList?.Select(x => x.Text).ToArray());
EditVariableDialogValueTxtBox.Text = newValues;
}
@@ -478,7 +476,7 @@ namespace EnvironmentVariablesUILib
var variable = EditVariableDialog.DataContext as Variable;
variable.ValuesList.Remove(listItem);
var newValues = string.Join(ValueListSeparator, variable.ValuesList?.Select(x => x.Text).ToArray());
var newValues = string.Join(";", variable.ValuesList?.Select(x => x.Text).ToArray());
EditVariableDialogValueTxtBox.Text = newValues;
}
@@ -494,7 +492,7 @@ namespace EnvironmentVariablesUILib
var index = variable.ValuesList.IndexOf(listItem);
variable.ValuesList.Insert(index, new Variable.ValuesListItem { Text = string.Empty });
var newValues = string.Join(ValueListSeparator, variable.ValuesList?.Select(x => x.Text).ToArray());
var newValues = string.Join(";", variable.ValuesList?.Select(x => x.Text).ToArray());
EditVariableDialogValueTxtBox.TextChanged -= EditVariableDialogValueTxtBox_TextChanged;
EditVariableDialogValueTxtBox.Text = newValues;
EditVariableDialogValueTxtBox.TextChanged += EditVariableDialogValueTxtBox_TextChanged;
@@ -512,7 +510,7 @@ namespace EnvironmentVariablesUILib
var index = variable.ValuesList.IndexOf(listItem);
variable.ValuesList.Insert(index + 1, new Variable.ValuesListItem { Text = string.Empty });
var newValues = string.Join(ValueListSeparator, variable.ValuesList?.Select(x => x.Text).ToArray());
var newValues = string.Join(";", variable.ValuesList?.Select(x => x.Text).ToArray());
EditVariableDialogValueTxtBox.TextChanged -= EditVariableDialogValueTxtBox_TextChanged;
EditVariableDialogValueTxtBox.Text = newValues;
EditVariableDialogValueTxtBox.TextChanged += EditVariableDialogValueTxtBox_TextChanged;
@@ -534,7 +532,7 @@ namespace EnvironmentVariablesUILib
listItem.Text = (sender as TextBox)?.Text;
var variable = EditVariableDialog.DataContext as Variable;
var newValues = string.Join(ValueListSeparator, variable.ValuesList?.Select(x => x.Text).ToArray());
var newValues = string.Join(";", variable.ValuesList?.Select(x => x.Text).ToArray());
EditVariableDialogValueTxtBox.TextChanged -= EditVariableDialogValueTxtBox_TextChanged;
EditVariableDialogValueTxtBox.Text = newValues;
EditVariableDialogValueTxtBox.TextChanged += EditVariableDialogValueTxtBox_TextChanged;
@@ -550,16 +548,5 @@ namespace EnvironmentVariablesUILib
CancelAddVariable();
ConfirmAddVariableBtn.IsEnabled = false;
}
private void EditVariableValuesList_DragItemsCompleted(ListViewBase sender, DragItemsCompletedEventArgs args)
{
if (EditVariableDialog.DataContext is Variable variable && variable.ValuesList != null)
{
var newValues = string.Join(ValueListSeparator, variable.ValuesList.Select(x => x.Text));
EditVariableDialogValueTxtBox.TextChanged -= EditVariableDialogValueTxtBox_TextChanged;
EditVariableDialogValueTxtBox.Text = newValues;
EditVariableDialogValueTxtBox.TextChanged += EditVariableDialogValueTxtBox_TextChanged;
}
}
}
}

View File

@@ -113,7 +113,7 @@ namespace Hosts.UITests
this.Find<NavigationViewItem>("Hosts File Editor").Click();
this.Find<ToggleSwitch>("Enable Hosts File Editor").Toggle(true);
this.Find<ToggleSwitch>("Hosts File Editor").Toggle(true);
this.Find<ToggleSwitch>("Launch as administrator").Toggle(launchAsAdmin);
this.Find<ToggleSwitch>("Show a warning at startup").Toggle(showWarning);

View File

@@ -0,0 +1,123 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" />
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|ARM64">
<Configuration>Debug</Configuration>
<Platform>ARM64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|ARM64">
<Configuration>Release</Configuration>
<Platform>ARM64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<VCProjectVersion>17.0</VCProjectVersion>
<Keyword>Win32Proj</Keyword>
<ProjectGuid>{79267138-2895-4346-9021-21408d65379f}</ProjectGuid>
<RootNamespace>LightSwitchLib</RootNamespace>
<WindowsTargetPlatformVersion>10.0.26100.0</WindowsTargetPlatformVersion>
<ProjectName>LightSwitchLib</ProjectName>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup>
<OutDir>..\..\..\..\$(Platform)\$(Configuration)\$(MSBuildProjectName)\</OutDir>
</PropertyGroup>
<ItemDefinitionGroup>
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<AdditionalIncludeDirectories>
./;
..\..\..\common;
..\..\..\common\logger;
..\..\..\common\utils;
..\..\..\..\deps\spdlog\include;
%(AdditionalIncludeDirectories)
</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="pch.h" />
<ClInclude Include="ThemeHelper.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="pch.cpp">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">Create</PrecompiledHeader>
</ClCompile>
<ClCompile Include="ThemeHelper.cpp" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\common\logger\logger.vcxproj">
<Project>{d9b8fc84-322a-4f9f-bbb9-20915c47ddfd}</Project>
</ProjectReference>
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<Import Project="..\..\..\..\deps\spdlog.props" />
<ImportGroup Label="ExtensionTargets">
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" />
</ImportGroup>
</Project>

View File

@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="Header Files">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
</Filter>
<Filter Include="Resource Files">
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
</Filter>
</ItemGroup>
<ItemGroup>
<ClInclude Include="pch.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="ThemeHelper.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="pch.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="ThemeHelper.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
</Project>

View File

@@ -1,9 +1,6 @@
#include <windows.h>
#include <logger/logger_settings.h>
#include <logger/logger.h>
#include <utils/logger_helper.h>
#include "pch.h"
#include "ThemeHelper.h"
#include <SettingsConstants.h>
#include <logger/logger.h>
// Controls changing the themes.
@@ -63,7 +60,7 @@ void SetSystemTheme(bool mode)
if (mode) // if are changing to light mode
{
ResetColorPrevalence();
Logger::info(L"[LightSwitchService] Reset ColorPrevalence to default when switching to light mode.");
Logger::info(L"[LightSwitchLib] Reset ColorPrevalence to default when switching to light mode.");
}
SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, 0, reinterpret_cast<LPARAM>(L"ImmersiveColorSet"), SMTO_ABORTIFHUNG, 5000, nullptr);
@@ -136,4 +133,4 @@ bool IsNightLightEnabled()
RegCloseKey(hKey);
return data[23] == 0x10 && data[24] == 0x00;
}
}

View File

@@ -0,0 +1,10 @@
#pragma once
inline constexpr wchar_t PERSONALIZATION_REGISTRY_PATH[] = L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize";
inline constexpr wchar_t NIGHT_LIGHT_REGISTRY_PATH[] = L"Software\\Microsoft\\Windows\\CurrentVersion\\CloudStore\\Store\\DefaultAccount\\Current\\default$windows.data.bluelightreduction.bluelightreductionstate\\windows.data.bluelightreduction.bluelightreductionstate";
void SetSystemTheme(bool isLight);
void SetAppsTheme(bool isLight);
bool GetCurrentSystemTheme();
bool GetCurrentAppsTheme();
bool IsNightLightEnabled();

View File

@@ -0,0 +1 @@
#include "pch.h"

View File

@@ -0,0 +1,5 @@
#pragma once
#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
#include <windows.h>
#include <vector>

View File

@@ -0,0 +1,22 @@
#include "pch.h"
#include "ThemeHelper.h"
extern "C" __declspec(dllexport) void __cdecl LightSwitch_SetSystemTheme(bool isLight)
{
SetSystemTheme(isLight);
}
extern "C" __declspec(dllexport) void __cdecl LightSwitch_SetAppsTheme(bool isLight)
{
SetAppsTheme(isLight);
}
extern "C" __declspec(dllexport) bool __cdecl LightSwitch_GetCurrentSystemTheme()
{
return GetCurrentSystemTheme();
}
extern "C" __declspec(dllexport) bool __cdecl LightSwitch_GetCurrentAppsTheme()
{
return GetCurrentAppsTheme();
}

View File

@@ -166,17 +166,17 @@
</ItemDefinitionGroup>
<ItemDefinitionGroup>
<ClCompile>
<AdditionalIncludeDirectories>..\..\..\common\inc;..\..\..\common\Telemetry;..\..\;..\..\..\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>..\LightSwitchLib;..\..\..\common\inc;..\..\..\common\Telemetry;..\..\;..\..\..\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="pch.h" />
<ClInclude Include="resource.h" />
<ClInclude Include="ThemeHelper.h" />
<ClInclude Include="trace.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="dllmain.cpp" />
<ClCompile Include="ExportedFunctions.cpp" />
<ClCompile Include="pch.cpp">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader>
@@ -187,7 +187,6 @@
<PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">pch.h</PrecompiledHeaderFile>
<PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">pch.h</PrecompiledHeaderFile>
</ClCompile>
<ClCompile Include="ThemeHelper.cpp" />
<ClCompile Include="trace.cpp" />
</ItemGroup>
<ItemGroup>
@@ -203,6 +202,9 @@
<ProjectReference Include="..\..\..\common\SettingsAPI\SettingsAPI.vcxproj">
<Project>{6955446d-23f7-4023-9bb3-8657f904af99}</Project>
</ProjectReference>
<ProjectReference Include="..\LightSwitchLib\LightSwitchLib.vcxproj">
<Project>{79267138-2895-4346-9021-21408d65379f}</Project>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />

View File

@@ -1,106 +0,0 @@
#include "pch.h"
#include <windows.h>
#include "ThemeHelper.h"
// Controls changing the themes.
static void ResetColorPrevalence()
{
HKEY hKey;
if (RegOpenKeyEx(HKEY_CURRENT_USER,
L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize",
0,
KEY_SET_VALUE,
&hKey) == ERROR_SUCCESS)
{
DWORD value = 0; // back to default value
RegSetValueEx(hKey, L"ColorPrevalence", 0, REG_DWORD, reinterpret_cast<const BYTE*>(&value), sizeof(value));
RegCloseKey(hKey);
SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, 0, reinterpret_cast<LPARAM>(L"ImmersiveColorSet"), SMTO_ABORTIFHUNG, 5000, nullptr);
SendMessageTimeout(HWND_BROADCAST, WM_THEMECHANGED, 0, 0, SMTO_ABORTIFHUNG, 5000, nullptr);
SendMessageTimeout(HWND_BROADCAST, WM_DWMCOLORIZATIONCOLORCHANGED, 0, 0, SMTO_ABORTIFHUNG, 5000, nullptr);
}
}
void SetAppsTheme(bool mode)
{
HKEY hKey;
if (RegOpenKeyEx(HKEY_CURRENT_USER,
L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize",
0,
KEY_SET_VALUE,
&hKey) == ERROR_SUCCESS)
{
DWORD value = mode;
RegSetValueEx(hKey, L"AppsUseLightTheme", 0, REG_DWORD, reinterpret_cast<const BYTE*>(&value), sizeof(value));
RegCloseKey(hKey);
SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, 0, reinterpret_cast<LPARAM>(L"ImmersiveColorSet"), SMTO_ABORTIFHUNG, 5000, nullptr);
SendMessageTimeout(HWND_BROADCAST, WM_THEMECHANGED, 0, 0, SMTO_ABORTIFHUNG, 5000, nullptr);
}
}
void SetSystemTheme(bool mode)
{
HKEY hKey;
if (RegOpenKeyEx(HKEY_CURRENT_USER,
L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize",
0,
KEY_SET_VALUE,
&hKey) == ERROR_SUCCESS)
{
DWORD value = mode;
RegSetValueEx(hKey, L"SystemUsesLightTheme", 0, REG_DWORD, reinterpret_cast<const BYTE*>(&value), sizeof(value));
RegCloseKey(hKey);
if (mode) // if are changing to light mode
{
ResetColorPrevalence();
}
SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, 0, reinterpret_cast<LPARAM>(L"ImmersiveColorSet"), SMTO_ABORTIFHUNG, 5000, nullptr);
SendMessageTimeout(HWND_BROADCAST, WM_THEMECHANGED, 0, 0, SMTO_ABORTIFHUNG, 5000, nullptr);
}
}
bool GetCurrentSystemTheme()
{
HKEY hKey;
DWORD value = 1; // default = light
DWORD size = sizeof(value);
if (RegOpenKeyEx(HKEY_CURRENT_USER,
L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize",
0,
KEY_READ,
&hKey) == ERROR_SUCCESS)
{
RegQueryValueEx(hKey, L"SystemUsesLightTheme", nullptr, nullptr, reinterpret_cast<LPBYTE>(&value), &size);
RegCloseKey(hKey);
}
return value == 1; // true = light, false = dark
}
bool GetCurrentAppsTheme()
{
HKEY hKey;
DWORD value = 1;
DWORD size = sizeof(value);
if (RegOpenKeyEx(HKEY_CURRENT_USER,
L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize",
0,
KEY_READ,
&hKey) == ERROR_SUCCESS)
{
RegQueryValueEx(hKey, L"AppsUseLightTheme", nullptr, nullptr, reinterpret_cast<LPBYTE>(&value), &size);
RegCloseKey(hKey);
}
return value == 1; // true = light, false = dark
}

View File

@@ -1,5 +0,0 @@
#pragma once
void SetSystemTheme(bool dark);
void SetAppsTheme(bool dark);
bool GetCurrentSystemTheme();
bool GetCurrentAppsTheme();

View File

@@ -55,6 +55,7 @@
<PreprocessorDefinitions>%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>
./../;
..\LightSwitchLib;
..\..\..\common;
..\..\..\common\logger;
..\..\..\common\utils;
@@ -78,7 +79,6 @@
<ClCompile Include="LightSwitchStateManager.cpp" />
<ClCompile Include="NightLightRegistryObserver.cpp" />
<ClCompile Include="SettingsConstants.cpp" />
<ClCompile Include="ThemeHelper.cpp" />
<ClCompile Include="ThemeScheduler.cpp" />
<ClCompile Include="trace.cpp" />
<ClCompile Include="WinHookEventIDs.cpp" />
@@ -93,7 +93,6 @@
<ClInclude Include="NightLightRegistryObserver.h" />
<ClInclude Include="SettingsConstants.h" />
<ClInclude Include="SettingsObserver.h" />
<ClInclude Include="ThemeHelper.h" />
<ClInclude Include="ThemeScheduler.h" />
<ClInclude Include="trace.h" />
<ClInclude Include="WinHookEventIDs.h" />
@@ -111,6 +110,9 @@
<ProjectReference Include="..\..\..\common\Telemetry\EtwTrace\EtwTrace.vcxproj">
<Project>{8f021b46-362b-485c-bfba-ccf83e820cbd}</Project>
</ProjectReference>
<ProjectReference Include="..\LightSwitchLib\LightSwitchLib.vcxproj">
<Project>{79267138-2895-4346-9021-21408d65379f}</Project>
</ProjectReference>
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<Import Project="..\..\..\..\deps\spdlog.props" />
@@ -118,4 +120,4 @@
<Import Project="..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" />
</ImportGroup>
</Project>
</Project>

View File

@@ -12,6 +12,3 @@ enum class SettingId
ChangeSystem,
ChangeApps
};
constexpr wchar_t PERSONALIZATION_REGISTRY_PATH[] = L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize";
constexpr wchar_t NIGHT_LIGHT_REGISTRY_PATH[] = L"Software\\Microsoft\\Windows\\CurrentVersion\\CloudStore\\Store\\DefaultAccount\\Current\\default$windows.data.bluelightreduction.bluelightreductionstate\\windows.data.bluelightreduction.bluelightreductionstate";

View File

@@ -1,6 +0,0 @@
#pragma once
void SetSystemTheme(bool dark);
void SetAppsTheme(bool dark);
bool GetCurrentSystemTheme();
bool GetCurrentAppsTheme();
bool IsNightLightEnabled();

View File

@@ -19,4 +19,9 @@
<PackageReference Include="MSTest" />
<ProjectReference Include="..\..\..\..\common\UITestAutomation\UITestAutomation.csproj" />
</ItemGroup>
<Target Name="CopyNativeDll" AfterTargets="Build">
<Copy SourceFiles="$(SolutionDir)$(Platform)\$(Configuration)\PowerToys.LightSwitchModuleInterface.dll" DestinationFolder="$(OutputPath)" SkipUnchangedFiles="true" />
<Copy SourceFiles="$(SolutionDir)$(Platform)\$(Configuration)\LightSwitchLib\LightSwitchLib.lib" DestinationFolder="$(OutputPath)" SkipUnchangedFiles="true" Condition="Exists('$(SolutionDir)$(Platform)\$(Configuration)\LightSwitchLib\LightSwitchLib.lib')" ContinueOnError="true" />
</Target>
</Project>

View File

@@ -5,6 +5,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using System.Windows.Forms;
using Microsoft.PowerToys.UITest;
@@ -17,6 +18,20 @@ namespace LightSwitch.UITests
{
private static readonly string[] ShortcutSeparators = { " + ", "+", " " };
[DllImport("PowerToys.LightSwitchModuleInterface.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern void LightSwitch_SetSystemTheme(bool isLight);
[DllImport("PowerToys.LightSwitchModuleInterface.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern void LightSwitch_SetAppsTheme(bool isLight);
[DllImport("PowerToys.LightSwitchModuleInterface.dll", CallingConvention = CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.I1)]
private static extern bool LightSwitch_GetCurrentSystemTheme();
[DllImport("PowerToys.LightSwitchModuleInterface.dll", CallingConvention = CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.I1)]
private static extern bool LightSwitch_GetCurrentAppsTheme();
/// <summary>
/// Performs common test initialization: navigate to settings, enable toggle, verify shortcut
/// </summary>
@@ -127,8 +142,7 @@ namespace LightSwitch.UITests
/// <param name="testBase">The test base instance</param>
public static void CleanupTest(UITestBase testBase)
{
// TODO: Make sure the task kills?
// CloseLightSwitch(testBase);
CloseLightSwitch(testBase);
// Ensure we're attached to settings after cleanup
try
@@ -141,6 +155,51 @@ namespace LightSwitch.UITests
}
}
/// <summary>
/// Switch to white/light theme for both system and apps
/// </summary>
/// <param name="testBase">The test base instance</param>
public static void CloseLightSwitch(UITestBase testBase)
{
// Kill LightSwitch process before setting themes
KillLightSwitchProcess();
// Set both themes to light (white)
SetSystemTheme(true);
SetAppsTheme(true);
}
/// <summary>
/// Kill the LightSwitch service process if it's running
/// </summary>
private static void KillLightSwitchProcess()
{
try
{
var processes = System.Diagnostics.Process.GetProcessesByName("PowerToys.LightSwitchService");
foreach (var process in processes)
{
try
{
process.Kill();
process.WaitForExit(2000);
}
catch
{
// Ignore errors killing individual processes
}
finally
{
process.Dispose();
}
}
}
catch
{
// Ignore errors enumerating processes
}
}
/// <summary>
/// Perform a update time test operation
/// </summary>
@@ -408,24 +467,22 @@ namespace LightSwitch.UITests
/* Helpers */
private static int GetSystemTheme()
{
using var key = Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize");
if (key is null)
{
return 1;
}
return (int)key.GetValue("SystemUsesLightTheme", 1);
return LightSwitch_GetCurrentSystemTheme() ? 1 : 0;
}
private static int GetAppsTheme()
{
using var key = Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize");
if (key is null)
{
return 1;
}
return LightSwitch_GetCurrentAppsTheme() ? 1 : 0;
}
return (int)key.GetValue("AppsUseLightTheme", 1);
private static void SetSystemTheme(bool isLight)
{
LightSwitch_SetSystemTheme(isLight);
}
private static void SetAppsTheme(bool isLight)
{
LightSwitch_SetAppsTheme(isLight);
}
private static string GetHelpTextValue(string helpText, string key)

View File

@@ -456,10 +456,11 @@ namespace MouseUtils.UITests
var groupAppearanceBehavior = foundCustom.Find(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouseAppearanceBehavior));
if (groupAppearanceBehavior != null)
{
// groupAppearanceBehavior.Click();
if (foundCustom.FindAll(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouseOverlayOpacity)).Count == 0)
var expandState = groupAppearanceBehavior.Selected;
if (!expandState)
{
groupAppearanceBehavior.Click();
Task.Delay(500).Wait();
}
// Set the BackGround color
@@ -541,15 +542,6 @@ namespace MouseUtils.UITests
Task.Delay(500).Wait();
spotlightColorButton.Click(false, 500, 1500);
// Set the overlay opacity to overlayOpacity%
var overlayOpacitySlider = foundCustom.Find<Slider>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouseOverlayOpacity));
Assert.IsNotNull(overlayOpacitySlider);
Assert.IsNotNull(settings.OverlayOpacity);
int overlayOpacityValue = int.Parse(settings.OverlayOpacity, CultureInfo.InvariantCulture);
overlayOpacitySlider.QuickSetValue(overlayOpacityValue);
Assert.AreEqual(settings.OverlayOpacity, overlayOpacitySlider.Text);
Task.Delay(1000).Wait();
// Set the Fade Initial zoom to 0
var spotlightInitialZoomSlider = foundCustom.Find<Slider>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouseSpotlightZoom));
Assert.IsNotNull(spotlightInitialZoomSlider);
@@ -592,7 +584,7 @@ namespace MouseUtils.UITests
// Assert.IsNull(animationDisabledWarning);
if (foundElements.Count != 0)
{
var openSettingsLink = foundCustom.Find<Element>("Open settings");
var openSettingsLink = foundCustom.Find<Element>("Open animation settings");
Assert.IsNotNull(openSettingsLink);
openSettingsLink.Click(false, 500, 3000);

View File

@@ -32,7 +32,6 @@ namespace MouseUtils.UITests
public const string FindMyMouseExcludedApps = "MouseUtils_FindMyMouseExcludedAppsId";
public const string FindMyMouseBackgroundColor = "MouseUtils_FindMyMouseBackgroundColorId";
public const string FindMyMouseSpotlightColor = "MouseUtils_FindMyMouseSpotlightColorId";
public const string FindMyMouseOverlayOpacity = "MouseUtils_FindMyMouseOverlayOpacityId";
public const string FindMyMouseSpotlightZoom = "MouseUtils_FindMyMouseSpotlightZoomId";
public const string FindMyMouseSpotlightRadius = "MouseUtils_FindMyMouseSpotlightRadiusId";
public const string FindMyMouseAnimationDuration = "MouseUtils_FindMyMouseAnimationDurationId";
@@ -72,10 +71,10 @@ namespace MouseUtils.UITests
private static readonly Dictionary<MouseUtils, string> MouseUtilUIToggleMap = new()
{
[MouseUtils.MouseHighlighter] = @"Enable Mouse Highlighter",
[MouseUtils.FindMyMouse] = @"Enable Find My Mouse",
[MouseUtils.MousePointerCrosshairs] = @"Enable Mouse Pointer Crosshairs",
[MouseUtils.MouseJump] = @"Enable Mouse Jump",
[MouseUtils.MouseHighlighter] = @"Mouse Highlighter",
[MouseUtils.FindMyMouse] = @"Find My Mouse",
[MouseUtils.MousePointerCrosshairs] = @"Mouse Pointer Crosshairs",
[MouseUtils.MouseJump] = @"Mouse Jump",
};
public static string GetMouseUtilUIName(MouseUtils element)

View File

@@ -10,10 +10,10 @@ public struct ApplicationWrapper
{
public struct WindowPositionWrapper
{
[JsonPropertyName("x")]
[JsonPropertyName("X")]
public int X { get; set; }
[JsonPropertyName("y")]
[JsonPropertyName("Y")]
public int Y { get; set; }
[JsonPropertyName("width")]

View File

@@ -57,7 +57,7 @@ public class WorkspacesSettingsTests : UITestBase
GoToSettingsPageAndEnable();
// Find the enable toggle
var enableToggle = Find<ToggleSwitch>("Enable Workspaces");
var enableToggle = Find<ToggleSwitch>("Workspaces");
Assert.IsNotNull(enableToggle, "Enable Workspaces toggle should exist");
Assert.IsTrue(enableToggle.IsOn, "Enable Workspaces toggle should be in the 'on' state");
@@ -80,7 +80,7 @@ public class WorkspacesSettingsTests : UITestBase
public void TestLaunchEditorByActivationShortcut()
{
// Ensure module is enabled
var enableToggle = Find<ToggleSwitch>("Enable Workspaces");
var enableToggle = Find<ToggleSwitch>("Workspaces");
if (!enableToggle.IsOn)
{
enableToggle.Click();
@@ -109,7 +109,7 @@ public class WorkspacesSettingsTests : UITestBase
public void TestDisabledModuleDoesNotLaunchByShortcut()
{
// Disable the module
var enableToggle = Find<ToggleSwitch>("Enable Workspaces");
var enableToggle = Find<ToggleSwitch>("Workspaces");
if (enableToggle.IsOn)
{
enableToggle.Click();
@@ -131,7 +131,7 @@ public class WorkspacesSettingsTests : UITestBase
RestartScopeExe();
NavigateToWorkspacesSettings();
enableToggle = Find<ToggleSwitch>("Enable Workspaces");
enableToggle = Find<ToggleSwitch>("Workspaces");
if (!enableToggle.IsOn)
{
enableToggle.Click();
@@ -174,7 +174,7 @@ public class WorkspacesSettingsTests : UITestBase
this.Find<NavigationViewItem>("Workspaces").Click();
var enableButton = this.Find<ToggleSwitch>("Enable Workspaces");
var enableButton = this.Find<ToggleSwitch>("Workspaces");
Assert.IsNotNull(enableButton, "Enable Workspaces toggle should exist");
if (!enableButton.IsOn)

View File

@@ -21,7 +21,7 @@ public sealed partial class BuiltInsCommandProvider : CommandProvider
public override ICommandItem[] TopLevelCommands() =>
[
new CommandItem(openSettings) { },
new CommandItem(_newExtension) { Title = _newExtension.Title, Subtitle = Properties.Resources.builtin_new_extension_subtitle },
new CommandItem(_newExtension) { Title = _newExtension.Title },
];
public override IFallbackCommandItem[] FallbackCommands() =>

View File

@@ -547,6 +547,15 @@ public partial class MainListPage : DynamicListPage,
// above "git" from "whatever"
max = max + extensionTitleMatch;
// Apply a penalty to fallback items so they rank below direct matches.
// Fallbacks that dynamically match queries (like RDP connections) should
// appear after apps and direct command matches.
if (isFallback && max > 1)
{
// Reduce fallback scores by 50% to prioritize direct matches
max = max * 0.5;
}
var matchSomething = max
+ (isAliasMatch ? 9001 : (isAliasSubstringMatch ? 1 : 0));

View File

@@ -2,7 +2,6 @@
// 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.Reflection;
using System.Runtime.CompilerServices;
namespace Microsoft.CmdPal.UI.Helpers;
@@ -18,19 +17,41 @@ internal static class BuildInfo
// Runtime AOT detection
public static bool IsNativeAot => !RuntimeFeature.IsDynamicCodeSupported;
// From assembly metadata (build-time values)
public static bool PublishTrimmed => GetBoolMetadata("PublishTrimmed", false);
// build-time values
public static bool PublishTrimmed
{
get
{
#if BUILD_INFO_PUBLISH_TRIMMED
return true;
#else
return false;
#endif
}
}
// From assembly metadata (build-time values)
public static bool PublishAot => GetBoolMetadata("PublishAot", false);
// build-time values
public static bool PublishAot
{
get
{
#if BUILD_INFO_PUBLISH_AOT
return true;
#else
return false;
#endif
}
}
public static bool IsCiBuild => GetBoolMetadata("CIBuild", false);
private static string? GetMetadata(string key) =>
Assembly.GetExecutingAssembly()
.GetCustomAttributes<AssemblyMetadataAttribute>()
.FirstOrDefault(a => a.Key == key)?.Value;
private static bool GetBoolMetadata(string key, bool defaultValue) =>
bool.TryParse(GetMetadata(key), out var result) ? result : defaultValue;
public static bool IsCiBuild
{
get
{
#if BUILD_INFO_CIBUILD
return true;
#else
return false;
#endif
}
}
}

View File

@@ -53,7 +53,7 @@
<PropertyGroup>
<!-- This disables the auto-generated main, so we can be single-instanced -->
<DefineConstants>DISABLE_XAML_GENERATED_MAIN</DefineConstants>
<DefineConstants>$(DefineConstants);DISABLE_XAML_GENERATED_MAIN</DefineConstants>
</PropertyGroup>
<!-- BODGY: XES Versioning and WinAppSDK get into a fight about the app manifest, which breaks WinAppSDK. -->
@@ -291,24 +291,15 @@
</ItemGroup>
<!-- </AdaptiveCardsWorkaround> -->
<!-- Metadata for build information -->
<ItemGroup>
<AssemblyAttribute Include="System.Reflection.AssemblyMetadataAttribute">
<_Parameter1>PublishTrimmed</_Parameter1>
<_Parameter2>$(PublishTrimmed)</_Parameter2>
</AssemblyAttribute>
<AssemblyAttribute Include="System.Reflection.AssemblyMetadataAttribute">
<_Parameter1>PublishAot</_Parameter1>
<_Parameter2>$(PublishAot)</_Parameter2>
</AssemblyAttribute>
<AssemblyAttribute Include="System.Reflection.AssemblyMetadataAttribute">
<_Parameter1>CIBuild</_Parameter1>
<_Parameter2>$(CIBuild)</_Parameter2>
</AssemblyAttribute>
<AssemblyAttribute Include="System.Reflection.AssemblyMetadataAttribute">
<_Parameter1>CommandPaletteBranding</_Parameter1>
<_Parameter2>$(CommandPaletteBranding)</_Parameter2>
</AssemblyAttribute>
</ItemGroup>
<!-- Build information -->
<PropertyGroup Condition=" '$(PublishAot)' == 'true' ">
<DefineConstants>$(DefineConstants);BUILD_INFO_PUBLISH_AOT</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(PublishTrimmed)' == 'true' ">
<DefineConstants>$(DefineConstants);BUILD_INFO_PUBLISH_TRIMMED</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(CIBuild)' == 'true' ">
<DefineConstants>$(DefineConstants);BUILD_INFO_CIBUILD</DefineConstants>
</PropertyGroup>
</Project>

View File

@@ -372,7 +372,7 @@ Right-click to remove the key combination, thereby deactivating the shortcut.</v
<value>Windows Command Palette</value>
</data>
<data name="Settings_GeneralPage_About_SettingsExpander.Description" xml:space="preserve">
<value>© 2025. All rights reserved.</value>
<value>© 2026. All rights reserved.</value>
</data>
<data name="Settings_GeneralPage_About_GithubLink_Hyperlink.Content" xml:space="preserve">
<value>View GitHub repository</value>

View File

@@ -90,20 +90,5 @@ namespace Microsoft.CmdPal.Ext.TimeDate.UnitTests
// Assert
Assert.IsFalse(string.IsNullOrEmpty(displayName));
}
[TestMethod]
public void GetTranslatedPluginDescriptionTest()
{
// Setup
var provider = new TimeDateCommandsProvider();
// Act
var commands = provider.TopLevelCommands();
var subtitle = commands[0].Subtitle;
// Assert
Assert.IsFalse(string.IsNullOrEmpty(subtitle));
Assert.IsTrue(subtitle.Contains("Show time and date values in different formats"));
}
}
}

View File

@@ -55,7 +55,7 @@ public class BasicTests : CommandPaletteTestBase
SetTimeAndDaterExtensionSearchBox("year");
Assert.IsNotNull(this.Find<NavigationViewItem>("2025"));
Assert.IsNotNull(this.Find<NavigationViewItem>("2026"));
}
[TestMethod]

View File

@@ -5,7 +5,7 @@
<XesUseOneStoreVersioning>true</XesUseOneStoreVersioning>
<XesBaseYearForStoreVersion>2025</XesBaseYearForStoreVersion>
<VersionMajor>0</VersionMajor>
<VersionMinor>7</VersionMinor>
<VersionMinor>8</VersionMinor>
<VersionInfoProductName>Microsoft Command Palette</VersionInfoProductName>
</PropertyGroup>
</Project>

View File

@@ -15,7 +15,6 @@ public partial class CalculatorCommandProvider : CommandProvider
private static ISettingsInterface settings = new SettingsManager();
private readonly ListItem _listItem = new(new CalculatorListPage(settings))
{
Subtitle = Resources.calculator_top_level_subtitle,
MoreCommands = [new CommandContextItem(((SettingsManager)settings).Settings.SettingsPage)],
};

View File

@@ -19,7 +19,6 @@ public partial class ClipboardHistoryCommandsProvider : CommandProvider
_clipboardHistoryListItem = new ListItem(new ClipboardHistoryListPage(_settingsManager))
{
Title = Properties.Resources.list_item_title,
Subtitle = Properties.Resources.list_item_subtitle,
Icon = Icons.ClipboardListIcon,
MoreCommands = [
new CommandContextItem(_settingsManager.Settings.SettingsPage),

View File

@@ -2,6 +2,7 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Globalization;
using Microsoft.CommandPalette.Extensions;
@@ -41,15 +42,19 @@ internal sealed partial class FancyZonesMonitorListItem : ListItem
public static Details BuildMonitorDetails(FancyZonesMonitorDescriptor monitor)
{
var currentVirtualDesktop = FancyZonesVirtualDesktop.GetCurrentVirtualDesktopIdString();
// Calculate physical resolution from logical pixels and DPI
var scaleFactor = monitor.Data.Dpi > 0 ? monitor.Data.Dpi / 96.0 : 1.0;
var physicalWidth = (int)Math.Round(monitor.Data.MonitorWidth * scaleFactor);
var physicalHeight = (int)Math.Round(monitor.Data.MonitorHeight * scaleFactor);
var resolution = $"{physicalWidth}\u00D7{physicalHeight}";
var tags = new List<IDetailsElement>
{
DetailTag(Resources.FancyZones_Monitor, monitor.Data.Monitor),
DetailTag(Resources.FancyZones_Instance, monitor.Data.MonitorInstanceId),
DetailTag(Resources.FancyZones_Serial, monitor.Data.MonitorSerialNumber),
DetailTag(Resources.FancyZones_Number, monitor.Data.MonitorNumber.ToString(CultureInfo.InvariantCulture)),
DetailTag(Resources.FancyZones_VirtualDesktop, currentVirtualDesktop),
DetailTag(Resources.FancyZones_WorkArea, $"{monitor.Data.LeftCoordinate},{monitor.Data.TopCoordinate} {monitor.Data.WorkAreaWidth}\u00D7{monitor.Data.WorkAreaHeight}"),
DetailTag(Resources.FancyZones_Resolution, $"{monitor.Data.MonitorWidth}\u00D7{monitor.Data.MonitorHeight}"),
DetailTag(Resources.FancyZones_Resolution, resolution),
DetailTag(Resources.FancyZones_DPI, monitor.Data.Dpi.ToString(CultureInfo.InvariantCulture)),
};

View File

@@ -41,16 +41,13 @@ internal static class FancyZonesDataService
try
{
if (!File.Exists(FZPaths.EditorParameters))
{
error = Resources.FancyZones_MonitorDataNotFound;
Logger.LogWarning($"TryGetMonitors: File not found. Path={FZPaths.EditorParameters}");
return false;
}
Logger.LogInfo($"TryGetMonitors: File exists, reading...");
var editorParams = FancyZonesDataIO.ReadEditorParameters();
Logger.LogInfo($"TryGetMonitors: ReadEditorParameters returned. Monitors={editorParams.Monitors?.Count ?? -1}");
// Request FancyZones to save current monitor configuration.
// The editor-parameters.json file is only written when:
// 1. Opening the FancyZones Editor
// 2. Receiving the WM_PRIV_SAVE_EDITOR_PARAMETERS message
// Without this, monitor changes (plug/unplug) won't be reflected in the file.
var editorParams = ReadEditorParametersWithRefresh();
Logger.LogInfo($"TryGetMonitors: ReadEditorParametersWithRefreshWithRefresh returned. Monitors={editorParams.Monitors?.Count ?? -1}");
var editorMonitors = editorParams.Monitors;
if (editorMonitors is null || editorMonitors.Count == 0)
@@ -74,6 +71,23 @@ internal static class FancyZonesDataService
}
}
/// <summary>
/// Requests FancyZones to save the current monitor configuration and reads the file.
/// This is a best-effort approach for performance: we send the save request and immediately
/// read the file without waiting. If the file hasn't been updated yet, the next call will
/// see the updated data since FancyZones processes the message asynchronously.
/// </summary>
private static EditorParameters.ParamsWrapper ReadEditorParametersWithRefresh()
{
// Request FancyZones to save the current monitor configuration.
// This is fire-and-forget for performance - we don't wait for the save to complete.
// If this is the first call after a monitor change, we may read stale data, but the
// next call will see the updated file since FancyZones will have processed the message.
FancyZonesNotifier.NotifySaveEditorParameters();
return FancyZonesDataIO.ReadEditorParameters();
}
public static IReadOnlyList<FancyZonesLayoutDescriptor> GetLayouts()
{
Logger.LogInfo($"GetLayouts: Starting. LayoutTemplatesPath={FZPaths.LayoutTemplates} CustomLayoutsPath={FZPaths.CustomLayouts}");

View File

@@ -19,8 +19,12 @@ internal readonly record struct FancyZonesMonitorDescriptor(
{
get
{
var size = $"{Data.MonitorWidth}×{Data.MonitorHeight}";
var scaling = Data.Dpi > 0 ? string.Format(CultureInfo.InvariantCulture, "{0}%", (int)Math.Round(Data.Dpi * 100 / 96.0)) : "n/a";
// MonitorWidth/Height are logical (DPI-scaled) pixels, calculate physical resolution
var scaleFactor = Data.Dpi > 0 ? Data.Dpi / 96.0 : 1.0;
var physicalWidth = (int)Math.Round(Data.MonitorWidth * scaleFactor);
var physicalHeight = (int)Math.Round(Data.MonitorHeight * scaleFactor);
var size = $"{physicalWidth}×{physicalHeight}";
var scaling = Data.Dpi > 0 ? string.Format(CultureInfo.InvariantCulture, "{0}%", (int)Math.Round(scaleFactor * 100)) : "n/a";
return $"{size} \u2022 {scaling}";
}
}

View File

@@ -10,13 +10,25 @@ namespace PowerToysExtension.Helpers;
internal static class FancyZonesNotifier
{
private const string AppliedLayoutsFileUpdateMessage = "{2ef2c8a7-e0d5-4f31-9ede-52aade2d284d}";
private const string SaveEditorParametersMessage = "{d8f9c0e3-5d77-4e83-8a4f-7c704c2bfb4a}";
private static readonly uint WmPrivAppliedLayoutsFileUpdate = RegisterWindowMessageW(AppliedLayoutsFileUpdateMessage);
private static readonly uint WmPrivSaveEditorParameters = RegisterWindowMessageW(SaveEditorParametersMessage);
public static void NotifyAppliedLayoutsChanged()
{
_ = PostMessageW(new IntPtr(0xFFFF), WmPrivAppliedLayoutsFileUpdate, UIntPtr.Zero, IntPtr.Zero);
}
/// <summary>
/// Notifies FancyZones to save the current monitor configuration to editor-parameters.json.
/// This is needed because FancyZones only writes this file when opening the editor or when explicitly requested.
/// </summary>
public static void NotifySaveEditorParameters()
{
_ = PostMessageW(new IntPtr(0xFFFF), WmPrivSaveEditorParameters, UIntPtr.Zero, IntPtr.Zero);
}
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern uint RegisterWindowMessageW(string lpString);

View File

@@ -485,12 +485,21 @@ internal static class FancyZonesThumbnailRenderer
private static List<NormalizedRect> GetFocusRects(int zoneCount)
{
// Focus layout parameters from FancyZonesEditor CanvasLayoutModel:
// - DefaultOffset = 100px from top-left (normalized: ~0.05 for typical screen)
// - OffsetShift = 50px per zone (normalized: ~0.025)
// - ZoneSizeMultiplier = 0.4 (zones are 40% of screen)
zoneCount = Math.Clamp(zoneCount, 1, 8);
var rects = new List<NormalizedRect>(zoneCount);
const float defaultOffset = 0.05f; // ~100px on 1920px screen
const float offsetShift = 0.025f; // ~50px on 1920px screen
const float zoneSize = 0.4f; // 40% of screen
for (var i = 0; i < zoneCount; i++)
{
var offset = i * 0.06f;
rects.Add(new NormalizedRect(0.1f + offset, 0.1f + offset, 0.8f, 0.8f));
var offset = i * offsetShift;
rects.Add(new NormalizedRect(defaultOffset + offset, defaultOffset + offset, zoneSize, zoneSize));
}
return rects;

View File

@@ -7,6 +7,7 @@
<OutputType>WinExe</OutputType>
<RootNamespace>PowerToysExtension</RootNamespace>
<ApplicationManifest>app.manifest</ApplicationManifest>
<ApplicationIcon>..\..\..\..\runner\svgs\icon.ico</ApplicationIcon>
<PublishProfile>win-$(Platform).pubxml</PublishProfile>
<EnableMsixTooling>false</EnableMsixTooling>
<WindowsPackageType>None</WindowsPackageType>

View File

@@ -31,7 +31,6 @@ public partial class RemoteDesktopCommandProvider : CommandProvider
listPageCommand = new CommandItem(listPage)
{
Subtitle = Resources.remotedesktop_subtitle,
Icon = Icons.RDPIcon,
MoreCommands = [
new CommandContextItem(settingsManager.Settings.SettingsPage),

View File

@@ -39,7 +39,6 @@ public partial class ShellCommandsProvider : CommandProvider
{
Icon = Icons.RunV2Icon,
Title = Resources.shell_command_name,
Subtitle = Resources.cmd_plugin_description,
MoreCommands = [
new CommandContextItem(Settings.SettingsPage),
],

View File

@@ -28,7 +28,6 @@ public sealed partial class TimeDateCommandsProvider : CommandProvider
{
Icon = _timeDateExtensionPage.Icon,
Title = Resources.Microsoft_plugin_timedate_plugin_name,
Subtitle = GetTranslatedPluginDescription(),
MoreCommands = [new CommandContextItem(_settingsManager.Settings.SettingsPage)],
};

View File

@@ -26,7 +26,6 @@ public partial class WindowWalkerCommandsProvider : CommandProvider
_windowWalkerPageItem = new CommandItem(new WindowWalkerListPage())
{
Title = Resources.window_walker_top_level_command_title,
Subtitle = Resources.windowwalker_name,
MoreCommands = [
new CommandContextItem(Settings.SettingsPage),
],

View File

@@ -30,7 +30,6 @@ public sealed partial class WindowsSettingsCommandsProvider : CommandProvider
_searchSettingsListItem = new CommandItem(new WindowsSettingsListPage(_windowsSettings))
{
Title = Resources.settings_title,
Subtitle = Resources.settings_subtitle,
};
_fallback = new(_windowsSettings);

View File

@@ -584,7 +584,7 @@ namespace UITests_FancyZones
}
windowingElement.Find<Element>("FancyZones").Click();
this.Find<ToggleSwitch>("Enable FancyZones").Toggle(true);
Find<ToggleSwitch>(By.AccessibilityId("EnableFancyZonesToggleSwitch")).Toggle(true);
if (isMax == true)
{
this.Find<Button>("Maximize").Click(); // maximize the window
@@ -661,7 +661,7 @@ namespace UITests_FancyZones
this.Find<NavigationViewItem>("Hosts File Editor").Click();
Task.Delay(1000).Wait();
this.Find<ToggleSwitch>("Enable Hosts File Editor").Toggle(true);
this.Find<ToggleSwitch>("Hosts File Editor").Toggle(true);
this.Find<ToggleSwitch>("Launch as administrator").Toggle(launchAsAdmin);
this.Find<ToggleSwitch>("Show a warning at startup").Toggle(showWarning);

View File

@@ -261,7 +261,7 @@ namespace UITests_FancyZones
}
this.Find<NavigationViewItem>("FancyZones").Click();
this.Find<ToggleSwitch>("Enable FancyZones").Toggle(true);
Find<ToggleSwitch>(By.AccessibilityId("EnableFancyZonesToggleSwitch")).Toggle(true);
this.Session.SetMainWindowSize(WindowSize.Large);
// fixed settings
@@ -322,7 +322,7 @@ namespace UITests_FancyZones
this.Find<NavigationViewItem>("Hosts File Editor").Click();
Task.Delay(1000).Wait();
this.Find<ToggleSwitch>("Enable Hosts File Editor").Toggle(true);
this.Find<ToggleSwitch>("Hosts File Editor").Toggle(true);
this.Find<ToggleSwitch>("Open as administrator").Toggle(launchAsAdmin);
this.Find<ToggleSwitch>("Show a warning at startup").Toggle(showWarning);

View File

@@ -9,6 +9,7 @@ using System.CommandLine.Invocation;
using System.Globalization;
using System.Linq;
using FancyZonesCLI.Utils;
using FancyZonesEditorCommon.Data;
using FancyZonesEditorCommon.Utils;
@@ -35,13 +36,19 @@ internal sealed partial class SetHotkeyCommand : FancyZonesBaseCommand
{
// FancyZones running guard is handled by FancyZonesBaseCommand.
int key = context.ParseResult.GetValueForArgument(_key);
string layout = context.ParseResult.GetValueForArgument(_layout);
string layoutInput = context.ParseResult.GetValueForArgument(_layout);
if (key < 0 || key > 9)
{
throw new InvalidOperationException(Properties.Resources.set_hotkey_error_invalid_key);
}
// Normalize GUID to Windows format with braces (supports input with or without braces)
if (!GuidHelper.TryNormalizeGuid(layoutInput, out string layout))
{
throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, Properties.Resources.set_hotkey_error_not_custom, layoutInput));
}
// Editor only allows assigning hotkeys to existing custom layouts.
var customLayouts = FancyZonesDataIO.ReadCustomLayouts();
@@ -60,7 +67,7 @@ internal sealed partial class SetHotkeyCommand : FancyZonesBaseCommand
if (!matchedLayout.HasValue)
{
throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, Properties.Resources.set_hotkey_error_not_custom, layout));
throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, Properties.Resources.set_hotkey_error_not_custom, layoutInput));
}
string layoutName = matchedLayout.Value.Name;

View File

@@ -140,9 +140,12 @@ internal sealed partial class SetLayoutCommand : FancyZonesBaseCommand
return null;
}
// Normalize GUID to Windows format with braces (supports input with or without braces)
string normalizedLayout = GuidHelper.NormalizeGuid(layout) ?? layout;
foreach (var customLayout in customLayouts.CustomLayouts)
{
if (customLayout.Uuid.Equals(layout, StringComparison.OrdinalIgnoreCase))
if (customLayout.Uuid.Equals(normalizedLayout, StringComparison.OrdinalIgnoreCase))
{
return customLayout;
}

View File

@@ -4,6 +4,7 @@
using System;
using System.CommandLine;
using System.Globalization;
using System.Linq;
namespace FancyZonesCLI.CommandLine;
@@ -13,15 +14,15 @@ internal static class FancyZonesCliUsage
public static void PrintUsage()
{
Console.OutputEncoding = System.Text.Encoding.UTF8;
Console.WriteLine("FancyZones CLI - Command line interface for FancyZones");
Console.WriteLine(Properties.Resources.usage_title);
Console.WriteLine();
var cmd = FancyZonesCliCommandFactory.CreateRootCommand();
Console.WriteLine("Usage: FancyZonesCLI [command] [options]");
Console.WriteLine(Properties.Resources.usage_syntax);
Console.WriteLine();
Console.WriteLine("Options:");
Console.WriteLine(Properties.Resources.usage_options);
foreach (var option in cmd.Options)
{
var aliases = string.Join(", ", option.Aliases);
@@ -30,7 +31,7 @@ internal static class FancyZonesCliUsage
}
Console.WriteLine();
Console.WriteLine("Commands:");
Console.WriteLine(Properties.Resources.usage_commands);
foreach (var command in cmd.Subcommands)
{
if (command.IsHidden)
@@ -51,7 +52,7 @@ internal static class FancyZonesCliUsage
}
Console.WriteLine();
Console.WriteLine("Examples:");
Console.WriteLine(Properties.Resources.usage_examples);
Console.WriteLine(" FancyZonesCLI --help");
Console.WriteLine(" FancyZonesCLI --version");
Console.WriteLine(" FancyZonesCLI get-monitors");
@@ -59,4 +60,135 @@ internal static class FancyZonesCliUsage
Console.WriteLine(" FancyZonesCLI set-layout <uuid> --monitor 1");
Console.WriteLine(" FancyZonesCLI get-hotkeys");
}
public static void PrintCommandUsage(string commandName)
{
Console.OutputEncoding = System.Text.Encoding.UTF8;
var rootCmd = FancyZonesCliCommandFactory.CreateRootCommand();
// Find matching subcommand by name or alias
var subcommand = rootCmd.Subcommands.FirstOrDefault(c =>
c.Aliases.Any(a => string.Equals(a, commandName, StringComparison.OrdinalIgnoreCase)));
if (subcommand == null)
{
Console.WriteLine(string.Format(CultureInfo.InvariantCulture, Properties.Resources.usage_unknown_command, commandName));
Console.WriteLine();
Console.WriteLine(Properties.Resources.usage_run_help);
return;
}
// Command name and description
Console.WriteLine($"{Properties.Resources.usage_command} {subcommand.Name}");
if (!string.IsNullOrEmpty(subcommand.Description))
{
Console.WriteLine($" {subcommand.Description}");
}
Console.WriteLine();
// Usage line
string argsLabel = string.Join(" ", subcommand.Arguments.Select(a => $"<{a.Name}>"));
string optionsLabel = subcommand.Options.Any() ? " [options]" : string.Empty;
Console.WriteLine($"Usage: FancyZonesCLI {subcommand.Name} {argsLabel}{optionsLabel}".TrimEnd());
Console.WriteLine();
// Aliases
var aliases = subcommand.Aliases.Where(a => !string.Equals(a, subcommand.Name, StringComparison.OrdinalIgnoreCase)).ToList();
if (aliases.Count > 0)
{
Console.WriteLine($"{Properties.Resources.usage_aliases} {string.Join(", ", aliases)}");
Console.WriteLine();
}
// Arguments
if (subcommand.Arguments.Any())
{
Console.WriteLine(Properties.Resources.usage_arguments);
foreach (var arg in subcommand.Arguments)
{
var argDescription = arg.Description ?? string.Empty;
Console.WriteLine($" <{arg.Name}>{(arg.Arity.MinimumNumberOfValues == 0 ? $" {Properties.Resources.usage_optional}" : string.Empty),-20} {argDescription}");
}
Console.WriteLine();
}
// Options
if (subcommand.Options.Any())
{
Console.WriteLine(Properties.Resources.usage_options);
foreach (var option in subcommand.Options)
{
var optAliases = string.Join(", ", option.Aliases);
var optDescription = option.Description ?? string.Empty;
Console.WriteLine($" {optAliases,-25} {optDescription}");
}
Console.WriteLine();
}
// Command-specific examples
PrintCommandExamples(subcommand.Name);
}
private static void PrintCommandExamples(string commandName)
{
Console.WriteLine(Properties.Resources.usage_examples);
switch (commandName.ToLowerInvariant())
{
case "get-monitors":
Console.WriteLine(" FancyZonesCLI get-monitors");
Console.WriteLine(" FancyZonesCLI m");
break;
case "get-layouts":
Console.WriteLine(" FancyZonesCLI get-layouts");
Console.WriteLine(" FancyZonesCLI ls");
break;
case "get-active-layout":
Console.WriteLine(" FancyZonesCLI get-active-layout");
Console.WriteLine(" FancyZonesCLI active");
break;
case "set-layout":
Console.WriteLine(" FancyZonesCLI set-layout focus");
Console.WriteLine(" FancyZonesCLI set-layout columns --monitor 1");
Console.WriteLine(" FancyZonesCLI set-layout {uuid} --all");
Console.WriteLine(" FancyZonesCLI s rows -m 2");
break;
case "open-editor":
Console.WriteLine(" FancyZonesCLI open-editor");
Console.WriteLine(" FancyZonesCLI e");
break;
case "open-settings":
Console.WriteLine(" FancyZonesCLI open-settings");
Console.WriteLine(" FancyZonesCLI settings");
break;
case "get-hotkeys":
Console.WriteLine(" FancyZonesCLI get-hotkeys");
Console.WriteLine(" FancyZonesCLI hk");
break;
case "set-hotkey":
Console.WriteLine(" FancyZonesCLI set-hotkey 1 {layout-uuid}");
Console.WriteLine(" FancyZonesCLI shk 2 0CEBCBA9-9C32-4395-B93E-DC77485AD6D0");
break;
case "remove-hotkey":
Console.WriteLine(" FancyZonesCLI remove-hotkey 1");
Console.WriteLine(" FancyZonesCLI rhk 2");
break;
default:
Console.WriteLine($" FancyZonesCLI {commandName}");
break;
}
}
}

View File

@@ -12,6 +12,8 @@ namespace FancyZonesCLI;
internal sealed class Program
{
private static readonly string[] HelpFlags = ["--help", "-h", "-?"];
private static async Task<int> Main(string[] args)
{
Logger.InitializeLogger();
@@ -21,14 +23,17 @@ internal sealed class Program
NativeMethods.InitializeWindowMessages();
// Intercept help requests early and print custom usage.
if (args.Any(a => string.Equals(a, "--help", StringComparison.OrdinalIgnoreCase) ||
string.Equals(a, "-h", StringComparison.OrdinalIgnoreCase) ||
string.Equals(a, "-?", StringComparison.OrdinalIgnoreCase)))
if (TryHandleHelpRequest(args))
{
FancyZonesCliUsage.PrintUsage();
return 0;
}
// Detect PowerShell script block expansion (when {} is interpreted as script block)
if (DetectPowerShellScriptBlockArgs(args))
{
return 1;
}
RootCommand rootCommand = FancyZonesCliCommandFactory.CreateRootCommand();
int exitCode = await rootCommand.InvokeAsync(args);
@@ -43,4 +48,69 @@ internal sealed class Program
return exitCode;
}
/// <summary>
/// Handles help requests for root command and subcommands.
/// </summary>
/// <returns>True if help was printed, false otherwise.</returns>
private static bool TryHandleHelpRequest(string[] args)
{
bool hasHelpFlag = args.Any(a => HelpFlags.Any(h => string.Equals(a, h, StringComparison.OrdinalIgnoreCase)));
if (!hasHelpFlag)
{
return false;
}
// Get non-help arguments to identify subcommand
var nonHelpArgs = args.Where(a => !HelpFlags.Any(h => string.Equals(a, h, StringComparison.OrdinalIgnoreCase))).ToArray();
if (nonHelpArgs.Length == 0)
{
// Root help: fancyzones cli --help
FancyZonesCliUsage.PrintUsage();
}
else
{
// Subcommand help: fancyzones cli <command> --help
string subcommandName = nonHelpArgs[0];
FancyZonesCliUsage.PrintCommandUsage(subcommandName);
}
return true;
}
/// <summary>
/// Detects when PowerShell interprets {GUID} as a script block and converts it to encoded command args.
/// This happens when users forget to quote GUIDs with braces in PowerShell.
/// </summary>
/// <returns>True if PowerShell script block args were detected, false otherwise.</returns>
private static bool DetectPowerShellScriptBlockArgs(string[] args)
{
// PowerShell converts {scriptblock} to: -encodedCommand <base64> -inputFormat xml -outputFormat text
bool hasEncodedCommand = args.Any(a => string.Equals(a, "-encodedCommand", StringComparison.OrdinalIgnoreCase));
bool hasInputFormat = args.Any(a => string.Equals(a, "-inputFormat", StringComparison.OrdinalIgnoreCase));
bool hasOutputFormat = args.Any(a => string.Equals(a, "-outputFormat", StringComparison.OrdinalIgnoreCase));
if (hasEncodedCommand || (hasInputFormat && hasOutputFormat))
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine(Properties.Resources.error_powershell_scriptblock_title);
Console.ResetColor();
Console.WriteLine();
Console.WriteLine(Properties.Resources.error_powershell_scriptblock_explanation);
Console.WriteLine(Properties.Resources.error_powershell_scriptblock_hint);
Console.WriteLine();
Console.WriteLine($" {Properties.Resources.error_powershell_scriptblock_option1}");
Console.WriteLine($" {Properties.Resources.error_powershell_scriptblock_option1_example}");
Console.WriteLine();
Console.WriteLine($" {Properties.Resources.error_powershell_scriptblock_option2}");
Console.WriteLine($" {Properties.Resources.error_powershell_scriptblock_option2_example}");
Console.WriteLine();
Logger.LogWarning("PowerShell script block expansion detected - user needs to quote GUID or omit braces");
return true;
}
return false;
}
}

View File

@@ -349,5 +349,113 @@ namespace FancyZonesCLI.Properties {
return ResourceManager.GetString("editor_params_timeout", resourceCulture);
}
}
internal static string error_powershell_scriptblock_title {
get {
return ResourceManager.GetString("error_powershell_scriptblock_title", resourceCulture);
}
}
internal static string error_powershell_scriptblock_explanation {
get {
return ResourceManager.GetString("error_powershell_scriptblock_explanation", resourceCulture);
}
}
internal static string error_powershell_scriptblock_hint {
get {
return ResourceManager.GetString("error_powershell_scriptblock_hint", resourceCulture);
}
}
internal static string error_powershell_scriptblock_option1 {
get {
return ResourceManager.GetString("error_powershell_scriptblock_option1", resourceCulture);
}
}
internal static string error_powershell_scriptblock_option1_example {
get {
return ResourceManager.GetString("error_powershell_scriptblock_option1_example", resourceCulture);
}
}
internal static string error_powershell_scriptblock_option2 {
get {
return ResourceManager.GetString("error_powershell_scriptblock_option2", resourceCulture);
}
}
internal static string error_powershell_scriptblock_option2_example {
get {
return ResourceManager.GetString("error_powershell_scriptblock_option2_example", resourceCulture);
}
}
internal static string usage_title {
get {
return ResourceManager.GetString("usage_title", resourceCulture);
}
}
internal static string usage_syntax {
get {
return ResourceManager.GetString("usage_syntax", resourceCulture);
}
}
internal static string usage_options {
get {
return ResourceManager.GetString("usage_options", resourceCulture);
}
}
internal static string usage_commands {
get {
return ResourceManager.GetString("usage_commands", resourceCulture);
}
}
internal static string usage_examples {
get {
return ResourceManager.GetString("usage_examples", resourceCulture);
}
}
internal static string usage_arguments {
get {
return ResourceManager.GetString("usage_arguments", resourceCulture);
}
}
internal static string usage_aliases {
get {
return ResourceManager.GetString("usage_aliases", resourceCulture);
}
}
internal static string usage_command {
get {
return ResourceManager.GetString("usage_command", resourceCulture);
}
}
internal static string usage_optional {
get {
return ResourceManager.GetString("usage_optional", resourceCulture);
}
}
internal static string usage_unknown_command {
get {
return ResourceManager.GetString("usage_unknown_command", resourceCulture);
}
}
internal static string usage_run_help {
get {
return ResourceManager.GetString("usage_run_help", resourceCulture);
}
}
}
}

View File

@@ -230,4 +230,62 @@ Tip: For templates, use the type name (e.g., 'focus', 'columns', 'rows', 'grid',
<data name="editor_params_timeout" xml:space="preserve">
<value>Could not get current monitor information (timed out after {0}ms waiting for '{1}').</value>
</data>
<!-- PowerShell Script Block Detection -->
<data name="error_powershell_scriptblock_title" xml:space="preserve">
<value>Error: Invalid GUID format detected.</value>
</data>
<data name="error_powershell_scriptblock_explanation" xml:space="preserve">
<value>PowerShell interprets curly braces {} as script blocks.</value>
</data>
<data name="error_powershell_scriptblock_hint" xml:space="preserve">
<value>Please quote your GUID or use it without braces:</value>
</data>
<data name="error_powershell_scriptblock_option1" xml:space="preserve">
<value>Option 1 - Quote the GUID:</value>
</data>
<data name="error_powershell_scriptblock_option1_example" xml:space="preserve">
<value>FancyZonesCLI shk 1 '{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}'</value>
</data>
<data name="error_powershell_scriptblock_option2" xml:space="preserve">
<value>Option 2 - Omit the braces (recommended):</value>
</data>
<data name="error_powershell_scriptblock_option2_example" xml:space="preserve">
<value>FancyZonesCLI shk 1 xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx</value>
</data>
<!-- CLI Usage -->
<data name="usage_title" xml:space="preserve">
<value>FancyZones CLI - Command line interface for FancyZones</value>
</data>
<data name="usage_syntax" xml:space="preserve">
<value>Usage: FancyZonesCLI [command] [options]</value>
</data>
<data name="usage_options" xml:space="preserve">
<value>Options:</value>
</data>
<data name="usage_commands" xml:space="preserve">
<value>Commands:</value>
</data>
<data name="usage_examples" xml:space="preserve">
<value>Examples:</value>
</data>
<data name="usage_arguments" xml:space="preserve">
<value>Arguments:</value>
</data>
<data name="usage_aliases" xml:space="preserve">
<value>Aliases:</value>
</data>
<data name="usage_command" xml:space="preserve">
<value>Command:</value>
</data>
<data name="usage_optional" xml:space="preserve">
<value>(optional)</value>
</data>
<data name="usage_unknown_command" xml:space="preserve">
<value>Unknown command: {0}</value>
</data>
<data name="usage_run_help" xml:space="preserve">
<value>Run 'FancyZonesCLI --help' to see available commands.</value>
</data>
</root>

View File

@@ -0,0 +1,52 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Diagnostics.CodeAnalysis;
#nullable enable
namespace FancyZonesCLI.Utils;
/// <summary>
/// Helper class for normalizing GUID strings to Windows format with braces.
/// Supports input with or without braces: both "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
/// and "{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}" are accepted.
/// </summary>
internal static class GuidHelper
{
/// <summary>
/// Normalizes a GUID string to Windows format with braces.
/// Returns null if the input is not a valid GUID.
/// </summary>
/// <param name="input">GUID string with or without braces.</param>
/// <returns>GUID in "{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}" format, or null if invalid.</returns>
public static string? NormalizeGuid(string? input)
{
if (string.IsNullOrWhiteSpace(input))
{
return null;
}
if (Guid.TryParse(input, out Guid guid))
{
// "B" format includes braces: {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}
return guid.ToString("B").ToUpperInvariant();
}
return null;
}
/// <summary>
/// Tries to normalize a GUID string to Windows format with braces.
/// </summary>
/// <param name="input">GUID string with or without braces.</param>
/// <param name="normalizedGuid">The normalized GUID string, or the original input if normalization fails.</param>
/// <returns>True if the input was successfully normalized; otherwise, false.</returns>
public static bool TryNormalizeGuid(string? input, [NotNullWhen(true)] out string? normalizedGuid)
{
normalizedGuid = NormalizeGuid(input);
return normalizedGuid != null;
}
}

View File

@@ -4,6 +4,7 @@
using System.Collections.Generic;
using System.Text.Json;
using System.Text.Json.Serialization;
using static FancyZonesEditorCommon.Data.CustomLayouts;
@@ -23,8 +24,10 @@ namespace FancyZonesEditorCommon.Data
{
public struct CanvasZoneWrapper
{
[JsonPropertyName("X")]
public int X { get; set; }
[JsonPropertyName("Y")]
public int Y { get; set; }
public int Width { get; set; }

View File

@@ -191,27 +191,22 @@ bool EditorParameters::Save(const WorkAreaConfiguration& configuration, OnThread
monitorJson.dpi = dpi;
MONITORINFOEX monitorInfo{};
// Get DPI-unaware values for dimensions (virtual coordinates for WPF sizing)
MONITORINFOEX monitorInfoUnaware{};
dpiUnawareThread.submit(OnThreadExecutor::task_t{ [&] {
monitorInfo.cbSize = sizeof(monitorInfo);
if (!GetMonitorInfo(monitor, &monitorInfo))
{
return;
}
monitorInfoUnaware.cbSize = sizeof(monitorInfoUnaware);
GetMonitorInfo(monitor, &monitorInfoUnaware);
} }).wait();
float width = static_cast<float>(monitorInfo.rcMonitor.right - monitorInfo.rcMonitor.left);
float height = static_cast<float>(monitorInfo.rcMonitor.bottom - monitorInfo.rcMonitor.top);
DPIAware::Convert(monitor, width, height);
// Dimensions in virtual coordinates (from DPI-unaware thread)
monitorJson.monitorWidth = monitorInfoUnaware.rcMonitor.right - monitorInfoUnaware.rcMonitor.left;
monitorJson.monitorHeight = monitorInfoUnaware.rcMonitor.bottom - monitorInfoUnaware.rcMonitor.top;
monitorJson.workAreaWidth = monitorInfoUnaware.rcWork.right - monitorInfoUnaware.rcWork.left;
monitorJson.workAreaHeight = monitorInfoUnaware.rcWork.bottom - monitorInfoUnaware.rcWork.top;
monitorJson.monitorWidth = static_cast<int>(std::roundf(width));
monitorJson.monitorHeight = static_cast<int>(std::roundf(height));
// use dpi-unaware values
monitorJson.top = monitorInfo.rcWork.top;
monitorJson.left = monitorInfo.rcWork.left;
monitorJson.workAreaWidth = monitorInfo.rcWork.right - monitorInfo.rcWork.left;
monitorJson.workAreaHeight = monitorInfo.rcWork.bottom - monitorInfo.rcWork.top;
// Position in virtual coordinates (matched by DPI-unaware context in WPF editor)
monitorJson.left = monitorInfoUnaware.rcWork.left;
monitorJson.top = monitorInfoUnaware.rcWork.top;
argsJson.monitors.emplace_back(std::move(monitorJson));
}

View File

@@ -67,10 +67,18 @@ namespace FancyZonesEditor.Models
Window.KeyUp += ((App)Application.Current).App_KeyUp;
Window.KeyDown += ((App)Application.Current).App_KeyDown;
// Store for DPI-unaware positioning
_virtualWorkArea = workArea;
// Set initial WPF properties
Window.Left = workArea.X;
Window.Top = workArea.Y;
Window.Width = workArea.Width;
Window.Height = workArea.Height;
// After HWND is created, reposition using DPI-unaware context
// This matches the C++ backend which uses a DPI-unaware thread
Window.SourceInitialized += OnWindowSourceInitialized;
}
public Monitor(string monitorName, string monitorInstanceId, string monitorSerialNumber, string virtualDesktop, int dpi, Rect workArea, Size monitorSize)
@@ -80,16 +88,33 @@ namespace FancyZonesEditor.Models
}
private LayoutSettings _settings;
private Rect _virtualWorkArea;
private void OnWindowSourceInitialized(object sender, EventArgs e)
{
// Reposition window using DPI-unaware context to match the virtual coordinates
// from the FancyZones C++ backend (which uses a DPI-unaware thread)
Utils.NativeMethods.SetWindowPositionDpiUnaware(
Window,
(int)_virtualWorkArea.X,
(int)_virtualWorkArea.Y,
(int)_virtualWorkArea.Width,
(int)_virtualWorkArea.Height);
}
public void Scale(double scaleFactor)
{
Device.Scale(scaleFactor);
var workArea = Device.WorkAreaRect;
Window.Left = workArea.X;
Window.Top = workArea.Y;
Window.Width = workArea.Width;
Window.Height = workArea.Height;
_virtualWorkArea = Device.WorkAreaRect;
// Use DPI-unaware positioning
Utils.NativeMethods.SetWindowPositionDpiUnaware(
Window,
(int)_virtualWorkArea.X,
(int)_virtualWorkArea.Y,
(int)_virtualWorkArea.Width,
(int)_virtualWorkArea.Height);
}
public void SetLayoutSettings(LayoutModel model)

View File

@@ -69,7 +69,11 @@ namespace FancyZonesEditor.Utils
}
else
{
return ScreenBoundsWidth + " × " + ScreenBoundsHeight;
// Convert virtual coordinates to physical resolution by applying DPI scale
double scale = DPI / 96.0;
int physicalWidth = (int)Math.Round(ScreenBoundsWidth * scale);
int physicalHeight = (int)Math.Round(ScreenBoundsHeight * scale);
return physicalWidth + " × " + physicalHeight;
}
}
}

View File

@@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation
// 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.
@@ -17,14 +17,48 @@ namespace FancyZonesEditor.Utils
[DllImport("user32.dll")]
private static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
[DllImport("user32.dll", SetLastError = true)]
private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);
[DllImport("user32.dll")]
private static extern IntPtr SetThreadDpiAwarenessContext(IntPtr dpiContext);
private const int GWL_EX_STYLE = -20;
private const int WS_EX_APPWINDOW = 0x00040000;
private const int WS_EX_TOOLWINDOW = 0x00000080;
private const uint SWP_NOZORDER = 0x0004;
private const uint SWP_NOACTIVATE = 0x0010;
private static readonly IntPtr DPI_AWARENESS_CONTEXT_UNAWARE = new IntPtr(-1);
public static void SetWindowStyleToolWindow(Window hwnd)
{
var helper = new WindowInteropHelper(hwnd).Handle;
_ = SetWindowLong(helper, GWL_EX_STYLE, (GetWindowLong(helper, GWL_EX_STYLE) | WS_EX_TOOLWINDOW) & ~WS_EX_APPWINDOW);
}
/// <summary>
/// Positions a WPF window using DPI-unaware context to match the virtual coordinates
/// from the FancyZones C++ backend (which uses a DPI-unaware thread).
/// This fixes overlay positioning on mixed-DPI multi-monitor setups.
/// </summary>
public static void SetWindowPositionDpiUnaware(Window window, int x, int y, int width, int height)
{
var helper = new WindowInteropHelper(window).Handle;
if (helper != IntPtr.Zero)
{
// Temporarily switch to DPI-unaware context to position window.
// This matches how the C++ backend gets coordinates via dpiUnawareThread.
IntPtr oldContext = SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_UNAWARE);
try
{
SetWindowPos(helper, IntPtr.Zero, x, y, width, height, SWP_NOZORDER | SWP_NOACTIVATE);
}
finally
{
SetThreadDpiAwarenessContext(oldContext);
}
}
}
}
}

View File

@@ -31,7 +31,7 @@ internal static class Program
Console.InputEncoding = Encoding.Unicode;
// Initialize logger to file (same as other modules)
CliLogger.Initialize("\\ImageResizer\\Logs");
CliLogger.Initialize("\\Image Resizer\\CLI");
CliLogger.Info($"ImageResizerCLI started with {args.Length} argument(s)");
try

View File

@@ -126,13 +126,10 @@ namespace ImageResizer.Properties
h => ncc.CollectionChanged -= h,
() => settings.CustomSize = new CustomSize());
Assert.AreEqual(NotifyCollectionChangedAction.Replace, result.Arguments.Action);
Assert.AreEqual(1, result.Arguments.NewItems.Count);
Assert.AreEqual(settings.CustomSize, result.Arguments.NewItems[0]);
Assert.AreEqual(0, result.Arguments.NewStartingIndex);
Assert.AreEqual(1, result.Arguments.OldItems.Count);
Assert.AreEqual(originalCustomSize, result.Arguments.OldItems[0]);
Assert.AreEqual(0, result.Arguments.OldStartingIndex);
// Reset is used instead of Replace to avoid ArgumentOutOfRangeException
// when notifying changes for virtual items (CustomSize/AiSize) that exist
// outside the bounds of the underlying _sizes collection.
Assert.AreEqual(NotifyCollectionChangedAction.Reset, result.Arguments.Action);
}
[TestMethod]

View File

@@ -20,7 +20,7 @@ namespace ImageResizer
{
public partial class App : Application, IDisposable
{
private const string LogSubFolder = "\\ImageResizer\\Logs";
private const string LogSubFolder = "\\Image Resizer\\Logs";
/// <summary>
/// Gets cached AI availability state, checked at app startup.
@@ -67,12 +67,27 @@ namespace ImageResizer
// Fix for .net 3.1.19 making Image Resizer not adapt to DPI changes.
NativeMethods.SetProcessDPIAware();
// TODO: Re-enable AI Super Resolution in next release by removing this #if block
// Temporarily disable AI Super Resolution feature (hide from UI but keep code)
#if true // Set to false to re-enable AI Super Resolution
AiAvailabilityState = AiAvailabilityState.NotSupported;
ResizeBatch.SetAiSuperResolutionService(Services.NoOpAiSuperResolutionService.Instance);
// Skip AI detection mode as well
if (e?.Args?.Length > 0 && e.Args[0] == "--detect-ai")
{
Services.AiAvailabilityCacheService.SaveCache(AiAvailabilityState.NotSupported);
Environment.Exit(0);
return;
}
#else
// Check for AI detection mode (called by Runner in background)
if (e?.Args?.Length > 0 && e.Args[0] == "--detect-ai")
{
RunAiDetectionMode();
return;
}
#endif
if (PowerToys.GPOWrapperProjection.GPOWrapper.GetConfiguredImageResizerEnabledValue() == PowerToys.GPOWrapperProjection.GpoRuleConfigured.Disabled)
{

View File

@@ -216,27 +216,15 @@ namespace ImageResizer.Properties
{
if (e.PropertyName == nameof(Models.CustomSize))
{
var oldCustomSize = _customSize;
_customSize = settings.CustomSize;
OnCollectionChanged(
new NotifyCollectionChangedEventArgs(
NotifyCollectionChangedAction.Replace,
_customSize,
oldCustomSize,
_sizes.Count));
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
else if (e.PropertyName == nameof(Models.AiSize))
{
var oldAiSize = _aiSize;
_aiSize = settings.AiSize;
OnCollectionChanged(
new NotifyCollectionChangedEventArgs(
NotifyCollectionChangedAction.Replace,
_aiSize,
oldAiSize,
_sizes.Count + 1));
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
else if (e.PropertyName == nameof(Sizes))
{

View File

@@ -388,6 +388,13 @@ namespace Peek.UI
IsErrorVisible = true;
}
public void ShowError(string message)
{
IsErrorVisible = false;
ErrorMessage = message;
IsErrorVisible = true;
}
private void NavigationThrottleTimer_Tick(object? sender, object e)
{
if (sender == null)

View File

@@ -50,7 +50,8 @@
Item="{x:Bind ViewModel.CurrentItem, Mode=OneWay}"
NumberOfFiles="{x:Bind ViewModel.DisplayItemCount, Mode=OneWay}"
PreviewSizeChanged="FilePreviewer_PreviewSizeChanged"
ScalingFactor="{x:Bind ViewModel.ScalingFactor, Mode=OneWay}" />
ScalingFactor="{x:Bind ViewModel.ScalingFactor, Mode=OneWay}"
Visibility="{x:Bind ContentVisibility(ViewModel.IsErrorVisible), Mode=OneWay}" />
<InfoBar
x:Name="ErrorInfoBar"
@@ -59,6 +60,7 @@
Grid.RowSpan="2"
Margin="4,0,4,6"
VerticalAlignment="Bottom"
Closed="ErrorInfoBar_Closed"
IsOpen="{x:Bind ViewModel.IsErrorVisible, Mode=TwoWay}"
Message="{x:Bind ViewModel.ErrorMessage, Mode=OneWay}"
Severity="Error" />

View File

@@ -14,6 +14,7 @@ using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Input;
using Peek.Common.Constants;
using Peek.Common.Extensions;
using Peek.Common.Helpers;
using Peek.FilePreviewer.Models;
using Peek.FilePreviewer.Previewers;
using Peek.UI.Extensions;
@@ -195,6 +196,20 @@ namespace Peek.UI
bootTime.Start();
ViewModel.Initialize(selectedItem);
// If no files were found (e.g., in virtual folders like Home/Recent), show an error
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;
}
ViewModel.ScalingFactor = this.GetMonitorScale();
this.Content.KeyUp += Content_KeyUp;
@@ -302,5 +317,24 @@ namespace Peek.UI
{
themeListener?.Dispose();
}
/// <summary>
/// Returns Visibility.Collapsed when error is showing, Visibility.Visible when not.
/// </summary>
public Visibility ContentVisibility(bool isErrorVisible)
{
return isErrorVisible ? Visibility.Collapsed : Visibility.Visible;
}
/// <summary>
/// Handle InfoBar closed - if there's no current item, close the window.
/// </summary>
private void ErrorInfoBar_Closed(InfoBar sender, InfoBarClosedEventArgs args)
{
if (ViewModel.CurrentItem == null)
{
Uninitialize();
}
}
}
}

View File

@@ -341,6 +341,10 @@
<value>No more files to preview.</value>
<comment>The message to show when there are no files remaining to preview.</comment>
</data>
<data name="NoFilesSelected" xml:space="preserve">
<value>No files selected or this folder is not supported for preview.</value>
<comment>Displayed when Peek is activated in a virtual folder (like Home or Recent) where file selection cannot be retrieved.</comment>
</data>
<data name="DeleteFileError_NotFound" xml:space="preserve">
<value>The file cannot be found. Please check if the file has been moved, renamed, or deleted.</value>
<comment>Displayed if the file or path was not found</comment>

View File

@@ -193,6 +193,102 @@ GeneralSettings get_general_settings()
return settings;
}
void apply_module_status_update(const json::JsonObject& module_config, bool save)
{
Logger::info(L"apply_module_status_update: {}", std::wstring{ module_config.ToString() });
// Expected format: {"ModuleName": true/false} - only one module per update
auto iter = module_config.First();
if (!iter.HasCurrent())
{
Logger::warn(L"apply_module_status_update: Empty module config");
return;
}
const auto& element = iter.Current();
const auto value = element.Value();
if (value.ValueType() != json::JsonValueType::Boolean)
{
Logger::warn(L"apply_module_status_update: Invalid value type for module status");
return;
}
const std::wstring name{ element.Key().c_str() };
if (modules().find(name) == modules().end())
{
Logger::warn(L"apply_module_status_update: Module {} not found", name);
return;
}
PowertoyModule& powertoy = modules().at(name);
const bool module_inst_enabled = powertoy->is_enabled();
bool target_enabled = value.GetBoolean();
auto gpo_rule = powertoy->gpo_policy_enabled_configuration();
if (gpo_rule == powertoys_gpo::gpo_rule_configured_enabled || gpo_rule == powertoys_gpo::gpo_rule_configured_disabled)
{
// Apply the GPO Rule.
target_enabled = gpo_rule == powertoys_gpo::gpo_rule_configured_enabled;
}
if (module_inst_enabled == target_enabled)
{
Logger::info(L"apply_module_status_update: Module {} already in target state {}", name, target_enabled);
return;
}
if (target_enabled)
{
Logger::info(L"apply_module_status_update: Enabling powertoy {}", name);
powertoy->enable();
auto& hkmng = HotkeyConflictDetector::HotkeyConflictManager::GetInstance();
hkmng.EnableHotkeyByModule(name);
// Trigger AI capability detection when ImageResizer is enabled
if (name == L"Image Resizer")
{
Logger::info(L"ImageResizer enabled, triggering AI capability detection");
DetectAiCapabilitiesAsync(true); // Skip settings check since we know it's being enabled
}
}
else
{
Logger::info(L"apply_module_status_update: Disabling powertoy {}", name);
powertoy->disable();
auto& hkmng = HotkeyConflictDetector::HotkeyConflictManager::GetInstance();
hkmng.DisableHotkeyByModule(name);
}
// Sync the hotkey state with the module state, so it can be removed for disabled modules.
powertoy.UpdateHotkeyEx();
if (save)
{
// Load existing settings and only update the specific module's enabled state
json::JsonObject current_settings = PTSettingsHelper::load_general_settings();
json::JsonObject enabled;
if (current_settings.HasKey(L"enabled"))
{
enabled = current_settings.GetNamedObject(L"enabled");
}
// Check if the saved state is different from the requested state
bool current_saved = enabled.HasKey(name) ? enabled.GetNamedBoolean(name, true) : true;
if (current_saved != target_enabled)
{
// Update only this module's enabled state
enabled.SetNamedValue(name, json::value(target_enabled));
current_settings.SetNamedValue(L"enabled", enabled);
PTSettingsHelper::save_general_settings(current_settings);
GeneralSettings settings_for_trace = get_general_settings();
Trace::SettingsChanged(settings_for_trace);
}
}
}
void apply_general_settings(const json::JsonObject& general_configs, bool save)
{
std::wstring old_settings_json_string;
@@ -367,11 +463,21 @@ void apply_general_settings(const json::JsonObject& general_configs, bool save)
if (json::has(general_configs, L"show_theme_adaptive_tray_icon", json::JsonValueType::Boolean))
{
bool new_theme_adaptive = general_configs.GetNamedBoolean(L"show_theme_adaptive_tray_icon");
Logger::info(L"apply_general_settings: show_theme_adaptive_tray_icon current={}, new={}",
show_theme_adaptive_tray_icon, new_theme_adaptive);
if (show_theme_adaptive_tray_icon != new_theme_adaptive)
{
show_theme_adaptive_tray_icon = new_theme_adaptive;
set_tray_icon_theme_adaptive(show_theme_adaptive_tray_icon);
}
else
{
Logger::info(L"apply_general_settings: show_theme_adaptive_tray_icon unchanged, skipping update");
}
}
else
{
Logger::warn(L"apply_general_settings: show_theme_adaptive_tray_icon not found in config");
}
if (json::has(general_configs, L"ignored_conflict_properties", json::JsonValueType::Object))

View File

@@ -38,4 +38,5 @@ struct GeneralSettings
json::JsonObject load_general_settings();
GeneralSettings get_general_settings();
void apply_general_settings(const json::JsonObject& general_configs, bool save = true);
void apply_module_status_update(const json::JsonObject& module_config, bool save = true);
void start_enabled_powertoys();

View File

@@ -215,6 +215,12 @@ void dispatch_received_json(const std::wstring& json_to_parse)
// current_settings_ipc->send(settings_string);
// }
}
else if (name == L"module_status")
{
// Handle single module enable/disable update
// Expected format: {"module_status": {"ModuleName": true/false}}
apply_module_status_update(value.GetObjectW());
}
else if (name == L"powertoys")
{
dispatch_json_config_to_modules(value.GetObjectW());

View File

@@ -273,12 +273,19 @@ static HICON get_icon(Theme theme)
{
std::wstring icon_path = get_module_folderpath();
icon_path += theme == Theme::Dark ? L"\\svgs\\PowerToysWhite.ico" : L"\\svgs\\PowerToysDark.ico";
return static_cast<HICON>(LoadImage(NULL,
Logger::trace(L"get_icon: Loading icon from path: {}", icon_path);
HICON icon = static_cast<HICON>(LoadImage(NULL,
icon_path.c_str(),
IMAGE_ICON,
0,
0,
LR_LOADFROMFILE | LR_DEFAULTSIZE | LR_SHARED));
if (!icon)
{
Logger::warn(L"get_icon: Failed to load icon from {}, error: {}", icon_path, GetLastError());
}
return icon;
}
@@ -374,13 +381,45 @@ void set_tray_icon_visible(bool shouldIconBeVisible)
void set_tray_icon_theme_adaptive(bool theme_adaptive)
{
theme_adaptive_enabled = theme_adaptive;
Logger::info(L"set_tray_icon_theme_adaptive: Called with theme_adaptive={}, current theme_adaptive_enabled={}",
theme_adaptive, theme_adaptive_enabled);
auto h_instance = reinterpret_cast<HINSTANCE>(&__ImageBase);
HICON const icon = theme_adaptive ? get_icon(theme_listener.AppTheme) : LoadIcon(h_instance, MAKEINTRESOURCE(APPICON));
HICON icon = nullptr;
if (theme_adaptive)
{
icon = get_icon(theme_listener.AppTheme);
if (!icon)
{
Logger::warn(L"set_tray_icon_theme_adaptive: Failed to load theme adaptive icon, falling back to default");
}
}
// If not requesting adaptive icon, or if adaptive icon failed to load, use default icon
if (!icon)
{
icon = LoadIcon(h_instance, MAKEINTRESOURCE(APPICON));
if (theme_adaptive && icon)
{
// We requested adaptive but had to fall back, so update the flag
theme_adaptive = false;
Logger::info(L"set_tray_icon_theme_adaptive: Using default icon as fallback");
}
}
theme_adaptive_enabled = theme_adaptive;
if (icon)
{
tray_icon_data.hIcon = icon;
Shell_NotifyIcon(NIM_MODIFY, &tray_icon_data);
BOOL result = Shell_NotifyIcon(NIM_MODIFY, &tray_icon_data);
Logger::info(L"set_tray_icon_theme_adaptive: Icon updated, theme_adaptive_enabled={}, Shell_NotifyIcon result={}",
theme_adaptive_enabled, result);
}
else
{
Logger::error(L"set_tray_icon_theme_adaptive: Failed to load any icon");
}
}

View File

@@ -7,6 +7,7 @@
<TargetFramework>net9.0-windows10.0.26100.0</TargetFramework>
<RootNamespace>Microsoft.PowerToys.QuickAccess</RootNamespace>
<AssemblyName>PowerToys.QuickAccess</AssemblyName>
<ApplicationIcon>..\..\runner\svgs\icon.ico</ApplicationIcon>
<UseWinUI>true</UseWinUI>
<WindowsPackageType>None</WindowsPackageType>
<WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>
@@ -50,8 +51,8 @@
</ItemGroup>
<ItemGroup>
<PRIResource Include="..\Settings.UI\Strings\en-us\Resources.resw">
<Link>Strings\en-us\Resources.resw</Link>
<PRIResource Include="..\Settings.UI\Strings\**\Resources.resw">
<Link>Strings\%(RecursiveDir)Resources.resw</Link>
</PRIResource>
</ItemGroup>

View File

@@ -51,6 +51,7 @@ public sealed partial class AppsListPage : Page
if (ViewModel != null)
{
ViewModel.DashboardSortOrder = DashboardSortOrder.Alphabetical;
((ToggleMenuFlyoutItem)sender).IsChecked = true;
}
}
@@ -59,6 +60,7 @@ public sealed partial class AppsListPage : Page
if (ViewModel != null)
{
ViewModel.DashboardSortOrder = DashboardSortOrder.ByStatus;
((ToggleMenuFlyoutItem)sender).IsChecked = true;
}
}
}

View File

@@ -3,7 +3,9 @@
// See the LICENSE file in the project root for more information.
using System.Threading.Tasks;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Library;
namespace Microsoft.PowerToys.QuickAccess.Services;
@@ -19,10 +21,10 @@ public interface IQuickAccessCoordinator
Task<bool> ShowDocumentationAsync();
void NotifyUserSettingsInteraction();
bool UpdateModuleEnabled(ModuleType moduleType, bool isEnabled);
void SendSortOrderUpdate(GeneralSettings generalSettings);
void ReportBug();
void OnModuleLaunched(ModuleType moduleType);

View File

@@ -55,37 +55,8 @@ internal sealed class QuickAccessCoordinator : IQuickAccessCoordinator, IDisposa
return Task.FromResult(false);
}
public void NotifyUserSettingsInteraction()
{
Logger.LogDebug("QuickAccessCoordinator.NotifyUserSettingsInteraction invoked.");
}
public bool UpdateModuleEnabled(ModuleType moduleType, bool isEnabled)
{
GeneralSettings? updatedSettings = null;
lock (_generalSettingsLock)
{
var repository = SettingsRepository<GeneralSettings>.GetInstance(_settingsUtils);
var generalSettings = repository.SettingsConfig;
var current = ModuleHelper.GetIsModuleEnabled(generalSettings, moduleType);
if (current == isEnabled)
{
return false;
}
ModuleHelper.SetIsModuleEnabled(generalSettings, moduleType, isEnabled);
_settingsUtils.SaveSettings(generalSettings.ToJsonString());
Logger.LogInfo($"QuickAccess updated module '{moduleType}' enabled state to {isEnabled}.");
updatedSettings = generalSettings;
}
if (updatedSettings != null)
{
SendGeneralSettingsUpdate(updatedSettings);
}
return true;
}
=> TrySendIpcMessage($"{{\"module_status\": {{\"{ModuleHelper.GetModuleKey(moduleType)}\": {isEnabled.ToString().ToLowerInvariant()}}}}}", "module status update");
public void ReportBug()
{
@@ -131,20 +102,10 @@ internal sealed class QuickAccessCoordinator : IQuickAccessCoordinator, IDisposa
Logger.LogDebug($"QuickAccessCoordinator received IPC payload: {message}");
}
private void SendGeneralSettingsUpdate(GeneralSettings updatedSettings)
public void SendSortOrderUpdate(GeneralSettings generalSettings)
{
string payload;
try
{
payload = new OutGoingGeneralSettings(updatedSettings).ToString();
}
catch (Exception ex)
{
Logger.LogError("QuickAccessCoordinator: failed to serialize general settings payload.", ex);
return;
}
TrySendIpcMessage(payload, "general settings update");
var outgoing = new OutGoingGeneralSettings(generalSettings);
TrySendIpcMessage(outgoing.ToString(), "sort order update");
}
private bool TrySendIpcMessage(string payload, string operationDescription)

View File

@@ -10,6 +10,7 @@ using global::PowerToys.GPOWrapper;
using ManagedCommon;
using Microsoft.PowerToys.QuickAccess.Helpers;
using Microsoft.PowerToys.QuickAccess.Services;
using Microsoft.PowerToys.Settings.UI.Controls;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Settings.UI.Library.Helpers;
using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
@@ -21,13 +22,18 @@ namespace Microsoft.PowerToys.QuickAccess.ViewModels;
public sealed class AllAppsViewModel : Observable
{
private readonly object _sortLock = new object();
private readonly IQuickAccessCoordinator _coordinator;
private readonly ISettingsRepository<GeneralSettings> _settingsRepository;
private readonly SettingsUtils _settingsUtils;
private readonly ResourceLoader _resourceLoader;
private readonly DispatcherQueue _dispatcherQueue;
private readonly List<FlyoutMenuItem> _allFlyoutMenuItems = new();
private GeneralSettings _generalSettings;
// Flag to prevent toggle operations during sorting to avoid race conditions.
private bool _isSorting;
public ObservableCollection<FlyoutMenuItem> FlyoutMenuItems { get; }
public DashboardSortOrder DashboardSortOrder
@@ -38,9 +44,9 @@ public sealed class AllAppsViewModel : Observable
if (_generalSettings.DashboardSortOrder != value)
{
_generalSettings.DashboardSortOrder = value;
_settingsUtils.SaveSettings(_generalSettings.ToJsonString(), _generalSettings.GetModuleName());
_coordinator.SendSortOrderUpdate(_generalSettings);
OnPropertyChanged();
RefreshFlyoutMenuItems();
SortFlyoutMenuItems();
}
}
}
@@ -52,21 +58,38 @@ public sealed class AllAppsViewModel : Observable
_settingsUtils = SettingsUtils.Default;
_settingsRepository = SettingsRepository<GeneralSettings>.GetInstance(_settingsUtils);
_generalSettings = _settingsRepository.SettingsConfig;
_generalSettings.AddEnabledModuleChangeNotification(ModuleEnabledChangedOnSettingsPage);
_settingsRepository.SettingsChanged += OnSettingsChanged;
_resourceLoader = Helpers.ResourceLoaderInstance.ResourceLoader;
FlyoutMenuItems = new ObservableCollection<FlyoutMenuItem>();
BuildFlyoutMenuItems();
RefreshFlyoutMenuItems();
}
private void BuildFlyoutMenuItems()
{
_allFlyoutMenuItems.Clear();
foreach (ModuleType moduleType in Enum.GetValues<ModuleType>())
{
if (moduleType == ModuleType.GeneralSettings)
{
continue;
}
_allFlyoutMenuItems.Add(new FlyoutMenuItem
{
Tag = moduleType,
EnabledChangedCallback = EnabledChangedOnUI,
});
}
}
private void OnSettingsChanged(GeneralSettings newSettings)
{
_dispatcherQueue.TryEnqueue(() =>
{
_generalSettings = newSettings;
_generalSettings.AddEnabledModuleChangeNotification(ModuleEnabledChangedOnSettingsPage);
OnPropertyChanged(nameof(DashboardSortOrder));
RefreshFlyoutMenuItems();
});
@@ -82,91 +105,90 @@ public sealed class AllAppsViewModel : Observable
private void RefreshFlyoutMenuItems()
{
var desiredItems = new List<FlyoutMenuItem>();
foreach (ModuleType moduleType in Enum.GetValues<ModuleType>())
foreach (var item in _allFlyoutMenuItems)
{
if (moduleType == ModuleType.GeneralSettings)
{
continue;
}
var moduleType = item.Tag;
var gpo = Helpers.ModuleGpoHelper.GetModuleGpoConfiguration(moduleType);
var isLocked = gpo is GpoRuleConfigured.Enabled or GpoRuleConfigured.Disabled;
var isEnabled = gpo == GpoRuleConfigured.Enabled || (!isLocked && Microsoft.PowerToys.Settings.UI.Library.Helpers.ModuleHelper.GetIsModuleEnabled(_generalSettings, moduleType));
var existingItem = FlyoutMenuItems.FirstOrDefault(x => x.Tag == moduleType);
item.Label = _resourceLoader.GetString(Microsoft.PowerToys.Settings.UI.Library.Helpers.ModuleHelper.GetModuleLabelResourceName(moduleType));
item.IsLocked = isLocked;
item.Icon = Microsoft.PowerToys.Settings.UI.Library.Helpers.ModuleHelper.GetModuleTypeFluentIconName(moduleType);
if (existingItem != null)
if (item.IsEnabled != isEnabled)
{
existingItem.Label = _resourceLoader.GetString(Microsoft.PowerToys.Settings.UI.Library.Helpers.ModuleHelper.GetModuleLabelResourceName(moduleType));
existingItem.IsLocked = isLocked;
existingItem.Icon = Microsoft.PowerToys.Settings.UI.Library.Helpers.ModuleHelper.GetModuleTypeFluentIconName(moduleType);
item.UpdateStatus(isEnabled);
}
}
if (existingItem.IsEnabled != isEnabled)
SortFlyoutMenuItems();
}
private void SortFlyoutMenuItems()
{
if (_isSorting)
{
return;
}
lock (_sortLock)
{
_isSorting = true;
try
{
var sortedItems = DashboardSortOrder switch
{
var callback = existingItem.EnabledChangedCallback;
existingItem.EnabledChangedCallback = null;
existingItem.IsEnabled = isEnabled;
existingItem.EnabledChangedCallback = callback;
DashboardSortOrder.ByStatus => _allFlyoutMenuItems.OrderByDescending(x => x.IsEnabled).ThenBy(x => x.Label).ToList(),
_ => _allFlyoutMenuItems.OrderBy(x => x.Label).ToList(),
};
if (FlyoutMenuItems.Count == 0)
{
foreach (var item in sortedItems)
{
FlyoutMenuItems.Add(item);
}
return;
}
desiredItems.Add(existingItem);
}
else
{
desiredItems.Add(new FlyoutMenuItem
for (int i = 0; i < sortedItems.Count; i++)
{
Label = _resourceLoader.GetString(Microsoft.PowerToys.Settings.UI.Library.Helpers.ModuleHelper.GetModuleLabelResourceName(moduleType)),
IsEnabled = isEnabled,
IsLocked = isLocked,
Tag = moduleType,
Icon = Microsoft.PowerToys.Settings.UI.Library.Helpers.ModuleHelper.GetModuleTypeFluentIconName(moduleType),
EnabledChangedCallback = EnabledChangedOnUI,
var item = sortedItems[i];
var oldIndex = FlyoutMenuItems.IndexOf(item);
if (oldIndex != -1 && oldIndex != i)
{
FlyoutMenuItems.Move(oldIndex, i);
}
}
}
finally
{
// Use dispatcher to reset flag after UI updates complete
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
{
_isSorting = false;
});
}
}
var sortedItems = DashboardSortOrder switch
{
DashboardSortOrder.ByStatus => desiredItems.OrderByDescending(x => x.IsEnabled).ThenBy(x => x.Label).ToList(),
_ => desiredItems.OrderBy(x => x.Label).ToList(),
};
for (int i = FlyoutMenuItems.Count - 1; i >= 0; i--)
{
if (!sortedItems.Contains(FlyoutMenuItems[i]))
{
FlyoutMenuItems.RemoveAt(i);
}
}
for (int i = 0; i < sortedItems.Count; i++)
{
var item = sortedItems[i];
var oldIndex = FlyoutMenuItems.IndexOf(item);
if (oldIndex < 0)
{
FlyoutMenuItems.Insert(i, item);
}
else if (oldIndex != i)
{
FlyoutMenuItems.Move(oldIndex, i);
}
}
}
private void EnabledChangedOnUI(FlyoutMenuItem item)
private void EnabledChangedOnUI(ModuleListItem item)
{
if (_coordinator.UpdateModuleEnabled(item.Tag, item.IsEnabled))
{
_coordinator.NotifyUserSettingsInteraction();
}
}
var flyoutItem = (FlyoutMenuItem)item;
var isEnabled = flyoutItem.IsEnabled;
private void ModuleEnabledChangedOnSettingsPage()
{
RefreshFlyoutMenuItems();
// Ignore toggle operations during sorting to prevent race conditions.
// Revert the toggle state since UI already changed due to TwoWay binding.
if (_isSorting)
{
flyoutItem.UpdateStatus(!isEnabled);
return;
}
_coordinator.UpdateModuleEnabled(flyoutItem.Tag, flyoutItem.IsEnabled);
SortFlyoutMenuItems();
}
}

View File

@@ -22,21 +22,6 @@ public sealed class FlyoutMenuItem : ModuleListItem
set => base.Tag = value;
}
public override bool IsEnabled
{
get => base.IsEnabled;
set
{
if (base.IsEnabled != value)
{
base.IsEnabled = value;
EnabledChangedCallback?.Invoke(this);
}
}
}
public Action<FlyoutMenuItem>? EnabledChangedCallback { get; set; }
public bool Visible
{
get => _visible;

View File

@@ -2,6 +2,7 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System; // For Action
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows.Input;
@@ -17,6 +18,22 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
private bool _isLocked;
private object? _tag;
private ICommand? _clickCommand;
private bool _isUpdating;
public Action<ModuleListItem>? EnabledChangedCallback { get; set; }
public void UpdateStatus(bool isEnabled)
{
_isUpdating = true;
try
{
IsEnabled = isEnabled;
}
finally
{
_isUpdating = false;
}
}
public virtual string Label
{
@@ -79,6 +96,11 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
{
_isEnabled = value;
OnPropertyChanged();
if (!_isUpdating)
{
EnabledChangedCallback?.Invoke(this);
}
}
}
}

View File

@@ -112,6 +112,13 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
eventHandle.Set();
}
return true;
case ModuleType.LightSwitch:
using (var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.LightSwitchToggleEvent()))
{
eventHandle.Set();
}
return true;
default:
return false;

View File

@@ -64,6 +64,7 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
AddFlyoutMenuItem(ModuleType.EnvironmentVariables);
AddFlyoutMenuItem(ModuleType.FancyZones);
AddFlyoutMenuItem(ModuleType.Hosts);
AddFlyoutMenuItem(ModuleType.LightSwitch);
AddFlyoutMenuItem(ModuleType.PowerLauncher);
AddFlyoutMenuItem(ModuleType.PowerOCR);
AddFlyoutMenuItem(ModuleType.RegistryPreview);
@@ -120,6 +121,7 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
{
ModuleType.ColorPicker => SettingsRepository<ColorPickerSettings>.GetInstance(SettingsUtils.Default).SettingsConfig.Properties.ActivationShortcut.ToString(),
ModuleType.FancyZones => SettingsRepository<FancyZonesSettings>.GetInstance(SettingsUtils.Default).SettingsConfig.Properties.FancyzonesEditorHotkey.Value.ToString(),
ModuleType.LightSwitch => SettingsRepository<LightSwitchSettings>.GetInstance(SettingsUtils.Default).SettingsConfig.Properties.ToggleThemeHotkey.Value.ToString(),
ModuleType.PowerLauncher => SettingsRepository<PowerLauncherSettings>.GetInstance(SettingsUtils.Default).SettingsConfig.Properties.OpenPowerLauncher.ToString(),
ModuleType.PowerOCR => SettingsRepository<PowerOcrSettings>.GetInstance(SettingsUtils.Default).SettingsConfig.Properties.ActivationShortcut.ToString(),
ModuleType.Workspaces => SettingsRepository<WorkspacesSettings>.GetInstance(SettingsUtils.Default).SettingsConfig.Properties.Hotkey.Value.ToString(),

View File

@@ -117,5 +117,47 @@ namespace Microsoft.PowerToys.Settings.UI.Library.Helpers
case ModuleType.GeneralSettings: generalSettingsConfig.EnableQuickAccess = isEnabled; break;
}
}
/// <summary>
/// Gets the module key name used in IPC messages and settings JSON.
/// These names match the JsonPropertyName attributes in EnabledModules class.
/// </summary>
public static string GetModuleKey(ModuleType moduleType)
{
return moduleType switch
{
ModuleType.AdvancedPaste => AdvancedPasteSettings.ModuleName,
ModuleType.AlwaysOnTop => AlwaysOnTopSettings.ModuleName,
ModuleType.Awake => AwakeSettings.ModuleName,
ModuleType.CmdPal => "CmdPal", // No dedicated settings class
ModuleType.ColorPicker => ColorPickerSettings.ModuleName,
ModuleType.CropAndLock => CropAndLockSettings.ModuleName,
ModuleType.CursorWrap => CursorWrapSettings.ModuleName,
ModuleType.EnvironmentVariables => EnvironmentVariablesSettings.ModuleName,
ModuleType.FancyZones => FancyZonesSettings.ModuleName,
ModuleType.FileLocksmith => FileLocksmithSettings.ModuleName,
ModuleType.FindMyMouse => FindMyMouseSettings.ModuleName,
ModuleType.Hosts => HostsSettings.ModuleName,
ModuleType.ImageResizer => ImageResizerSettings.ModuleName,
ModuleType.KeyboardManager => KeyboardManagerSettings.ModuleName,
ModuleType.LightSwitch => LightSwitchSettings.ModuleName,
ModuleType.MouseHighlighter => MouseHighlighterSettings.ModuleName,
ModuleType.MouseJump => MouseJumpSettings.ModuleName,
ModuleType.MousePointerCrosshairs => MousePointerCrosshairsSettings.ModuleName,
ModuleType.MouseWithoutBorders => MouseWithoutBordersSettings.ModuleName,
ModuleType.NewPlus => NewPlusSettings.ModuleName,
ModuleType.Peek => PeekSettings.ModuleName,
ModuleType.PowerRename => PowerRenameSettings.ModuleName,
ModuleType.PowerLauncher => PowerLauncherSettings.ModuleName,
ModuleType.PowerAccent => PowerAccentSettings.ModuleName,
ModuleType.RegistryPreview => RegistryPreviewSettings.ModuleName,
ModuleType.MeasureTool => MeasureToolSettings.ModuleName,
ModuleType.ShortcutGuide => ShortcutGuideSettings.ModuleName,
ModuleType.PowerOCR => PowerOcrSettings.ModuleName,
ModuleType.Workspaces => WorkspacesSettings.ModuleName,
ModuleType.ZoomIt => ZoomItSettings.ModuleName,
_ => moduleType.ToString(),
};
}
}
}

View File

@@ -11,7 +11,7 @@ using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Settings.UI.Library.Helpers;
using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
namespace Settings.UI.Library
namespace Microsoft.PowerToys.Settings.UI.Library
{
public class LightSwitchSettings : BasePTModuleSettings, ISettingsConfig, ICloneable, IHotkeyConfig
{

View File

@@ -56,7 +56,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
[JsonSerializable(typeof(HostsSettings))]
[JsonSerializable(typeof(ImageResizerSettings))]
[JsonSerializable(typeof(KeyboardManagerSettings))]
[JsonSerializable(typeof(SettingsUILibrary.LightSwitchSettings))]
[JsonSerializable(typeof(LightSwitchSettings))]
[JsonSerializable(typeof(MeasureToolSettings))]
[JsonSerializable(typeof(MouseHighlighterSettings))]
[JsonSerializable(typeof(MouseJumpSettings))]

View File

@@ -32,7 +32,6 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Enums
MeasureTool,
Hosts,
Workspaces,
WhatsNew,
RegistryPreview,
NewPlus,
ZoomIt,

View File

@@ -23,7 +23,7 @@ namespace Microsoft.PowerToys.Settings.UI.SerializationContext;
[JsonSerializable(typeof(FileLocksmithSettings))]
[JsonSerializable(typeof(FindMyMouseSettings))]
[JsonSerializable(typeof(IList<PowerToysReleaseInfo>))]
[JsonSerializable(typeof(SettingsUILibrary.LightSwitchSettings))]
[JsonSerializable(typeof(LightSwitchSettings))]
[JsonSerializable(typeof(MeasureToolSettings))]
[JsonSerializable(typeof(MouseHighlighterSettings))]
[JsonSerializable(typeof(MouseJumpSettings))]

View File

@@ -9,7 +9,6 @@ using System.IO;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Helpers;
using Microsoft.PowerToys.Settings.UI.Library;
@@ -227,7 +226,6 @@ namespace Microsoft.PowerToys.Settings.UI
{
settingsWindow = new MainWindow();
settingsWindow.Activate();
settingsWindow.ExtendsContentIntoTitleBar = true;
settingsWindow.NavigateToSection(StartupPage);
// https://github.com/microsoft/microsoft-ui-xaml/issues/7595 - Activate doesn't bring window to the foreground
@@ -257,11 +255,10 @@ namespace Microsoft.PowerToys.Settings.UI
else if (ShowScoobe)
{
PowerToysTelemetry.Log.WriteEvent(new ScoobeStartedEvent());
OobeWindow scoobeWindow = new OobeWindow(OOBE.Enums.PowerToysModules.WhatsNew);
scoobeWindow.Activate();
scoobeWindow.ExtendsContentIntoTitleBar = true;
ScoobeWindow newScoobeWindow = new ScoobeWindow();
newScoobeWindow.Activate();
WindowHelpers.ForceTopBorder1PixelInsetOnWindows10(WindowNative.GetWindowHandle(settingsWindow));
SetOobeWindow(scoobeWindow);
SetScoobeWindow(newScoobeWindow);
}
}
}
@@ -339,6 +336,7 @@ namespace Microsoft.PowerToys.Settings.UI
private static MainWindow settingsWindow;
private static OobeWindow oobeWindow;
private static ScoobeWindow scoobeWindow;
public static void ClearSettingsWindow()
{
@@ -365,6 +363,21 @@ namespace Microsoft.PowerToys.Settings.UI
oobeWindow = null;
}
public static ScoobeWindow GetScoobeWindow()
{
return scoobeWindow;
}
public static void SetScoobeWindow(ScoobeWindow window)
{
scoobeWindow = window;
}
public static void ClearScoobeWindow()
{
scoobeWindow = null;
}
public static Type GetPage(string settingWindow)
{
switch (settingWindow)

View File

@@ -20,9 +20,6 @@ using WinUIEx;
namespace Microsoft.PowerToys.Settings.UI
{
/// <summary>
/// An empty window that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class MainWindow : WindowEx
{
public MainWindow(bool createHidden = false)
@@ -35,10 +32,12 @@ namespace Microsoft.PowerToys.Settings.UI
App.ThemeService.ThemeChanged += OnThemeChanged;
App.ThemeService.ApplyTheme();
this.ExtendsContentIntoTitleBar = true;
ShellPage.SetElevationStatus(App.IsElevated);
ShellPage.SetIsUserAnAdmin(App.IsUserAnAdmin);
var hWnd = WinRT.Interop.WindowNative.GetWindowHandle(this);
var hWnd = WindowNative.GetWindowHandle(this);
var placement = WindowHelper.DeserializePlacementOrDefault(hWnd);
if (createHidden)
{
@@ -121,16 +120,12 @@ namespace Microsoft.PowerToys.Settings.UI
// open whats new window
ShellPage.SetOpenWhatIsNewCallback(() =>
{
if (App.GetOobeWindow() == null)
if (App.GetScoobeWindow() == null)
{
App.SetOobeWindow(new OobeWindow(OOBE.Enums.PowerToysModules.WhatsNew));
}
else
{
App.GetOobeWindow().SetAppWindow(OOBE.Enums.PowerToysModules.WhatsNew);
App.SetScoobeWindow(new ScoobeWindow());
}
App.GetOobeWindow().Activate();
App.GetScoobeWindow().Activate();
});
this.InitializeComponent();
@@ -187,7 +182,7 @@ namespace Microsoft.PowerToys.Settings.UI
var hWnd = WinRT.Interop.WindowNative.GetWindowHandle(this);
WindowHelper.SerializePlacement(hWnd);
if (App.GetOobeWindow() == null)
if (App.GetOobeWindow() == null && App.GetScoobeWindow() == null)
{
App.ClearSettingsWindow();
}

View File

@@ -1,60 +1,44 @@
<UserControl
<Page
x:Class="Microsoft.PowerToys.Settings.UI.OOBE.Views.OobeShellPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:animations="using:CommunityToolkit.WinUI.Animations"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="using:CommunityToolkit.WinUI"
HighContrastAdjustment="None"
Loaded="ShellPage_Loaded"
mc:Ignorable="d">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="48" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Button
x:Name="PaneToggleBtn"
Width="48"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Click="PaneToggleBtn_Click"
Style="{StaticResource PaneToggleButtonStyle}" />
<Grid
<TitleBar
x:Name="AppTitleBar"
Height="48"
Margin="48,0,0,0"
VerticalAlignment="Center"
IsHitTestVisible="True">
<animations:Implicit.Animations>
<animations:OffsetAnimation Duration="0:0:0.3" />
</animations:Implicit.Animations>
<StackPanel Orientation="Horizontal">
<Image
Width="16"
x:Uid="OobeWindow_TitleTxt"
IsBackButtonVisible="False"
IsPaneToggleButtonVisible="False"
PaneToggleRequested="TitleBar_PaneButtonClick">
<!-- This is a workaround for https://github.com/microsoft/microsoft-ui-xaml/issues/10374, once fixed we should just be using IconSource -->
<TitleBar.LeftHeader>
<ImageIcon
x:Name="TitleBarIcon"
Height="16"
Margin="16,0,0,0"
Source="/Assets/Settings/icon.ico" />
<TextBlock
x:Name="AppTitleBarText"
x:Uid="OobeWindow_TitleTxt"
VerticalAlignment="Center"
Style="{StaticResource CaptionTextBlockStyle}"
TextWrapping="NoWrap" />
</StackPanel>
</Grid>
</TitleBar.LeftHeader>
</TitleBar>
<NavigationView
x:Name="navigationView"
Grid.Row="1"
CompactModeThresholdWidth="1007"
DisplayModeChanged="NavigationView_DisplayModeChanged"
ExpandedModeThresholdWidth="1007"
IsBackButtonVisible="Collapsed"
IsPaneOpen="True"
IsPaneToggleButtonVisible="False"
IsSettingsVisible="False"
OpenPaneLength="296"
PaneDisplayMode="Left"
SelectionChanged="NavigationView_SelectionChanged">
<NavigationView.MenuItems>
<NavigationViewItem
@@ -174,34 +158,16 @@
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/ZoomIt.png}"
Tag="ZoomIt" />
</NavigationView.MenuItems>
<NavigationView.FooterMenuItems>
<NavigationView.PaneFooter>
<NavigationViewItem
x:Uid="Shell_WhatsNew"
AutomationProperties.AutomationId="WhatIsNewNavItem"
Icon="{ui:FontIcon Glyph=&#xE789;}"
Tag="WhatsNew" />
</NavigationView.FooterMenuItems>
Tapped="WhatIsNewItem_Tapped" />
</NavigationView.PaneFooter>
<NavigationView.Content>
<Frame x:Name="NavigationFrame" />
</NavigationView.Content>
</NavigationView>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="LayoutVisualStates">
<VisualState x:Name="WideLayout">
<VisualState.StateTriggers>
<AdaptiveTrigger MinWindowWidth="720" />
</VisualState.StateTriggers>
</VisualState>
<VisualState x:Name="SmallLayout">
<VisualState.StateTriggers>
<AdaptiveTrigger MinWindowWidth="600" />
<AdaptiveTrigger MinWindowWidth="0" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="navigationView.PaneDisplayMode" Value="LeftMinimal" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
</UserControl>
</Page>

View File

@@ -5,18 +5,17 @@
using System;
using System.Collections.ObjectModel;
using System.Globalization;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Settings.UI.OOBE.Enums;
using Microsoft.PowerToys.Settings.UI.OOBE.ViewModel;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using WinRT.Interop;
using Microsoft.UI.Xaml.Input;
namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
{
public sealed partial class OobeShellPage : UserControl
public sealed partial class OobeShellPage : Page
{
public static Func<string> RunSharedEventCallback { get; set; }
@@ -63,7 +62,6 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
// NOTE: Experimentation for OOBE is currently turned off on server side. Keeping this code in a comment to allow future experiments.
// ExperimentationToggleSwitchEnabled = SettingsRepository<GeneralSettings>.GetInstance(settingsUtils).SettingsConfig.EnableExperimentation;
SetTitleBar();
DataContext = ViewModel;
OobeShellHandler = this;
Modules = new ObservableCollection<OobePowerToysModule>();
@@ -202,12 +200,6 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
IsNew = true,
});
Modules.Insert((int)PowerToysModules.WhatsNew, new OobePowerToysModule()
{
ModuleName = "WhatsNew",
IsNew = false,
});
Modules.Insert((int)PowerToysModules.RegistryPreview, new OobePowerToysModule()
{
ModuleName = "RegistryPreview",
@@ -229,7 +221,7 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
public void OnClosing()
{
Microsoft.UI.Xaml.Controls.NavigationViewItem selectedItem = this.navigationView.SelectedItem as Microsoft.UI.Xaml.Controls.NavigationViewItem;
NavigationViewItem selectedItem = this.navigationView.SelectedItem as NavigationViewItem;
if (selectedItem != null)
{
Modules[(int)(PowerToysModules)Enum.Parse(typeof(PowerToysModules), (string)selectedItem.Tag, true)].LogClosingModuleEvent();
@@ -238,19 +230,22 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
public void NavigateToModule(PowerToysModules selectedModule)
{
if (selectedModule == PowerToysModules.WhatsNew)
{
navigationView.SelectedItem = navigationView.FooterMenuItems[0];
}
else
{
navigationView.SelectedItem = navigationView.MenuItems[(int)selectedModule];
}
navigationView.SelectedItem = navigationView.MenuItems[(int)selectedModule];
}
private void NavigationView_SelectionChanged(Microsoft.UI.Xaml.Controls.NavigationView sender, Microsoft.UI.Xaml.Controls.NavigationViewSelectionChangedEventArgs args)
private static void OpenScoobeWindow()
{
Microsoft.UI.Xaml.Controls.NavigationViewItem selectedItem = args.SelectedItem as Microsoft.UI.Xaml.Controls.NavigationViewItem;
if (App.GetScoobeWindow() == null)
{
App.SetScoobeWindow(new ScoobeWindow());
}
App.GetScoobeWindow().Activate();
}
private void NavigationView_SelectionChanged(NavigationView sender, NavigationViewSelectionChangedEventArgs args)
{
NavigationViewItem selectedItem = args.SelectedItem as NavigationViewItem;
if (selectedItem != null)
{
@@ -278,7 +273,7 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
break;
}
*/
case "WhatsNew": NavigationFrame.Navigate(typeof(OobeWhatsNew)); break;
case "AdvancedPaste": NavigationFrame.Navigate(typeof(OobeAdvancedPaste)); break;
case "AlwaysOnTop": NavigationFrame.Navigate(typeof(OobeAlwaysOnTop)); break;
case "Awake": NavigationFrame.Navigate(typeof(OobeAwake)); break;
@@ -311,43 +306,37 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
}
}
private void SetTitleBar()
{
var u = App.GetOobeWindow();
if (u != null)
{
// A custom title bar is required for full window theme and Mica support.
// https://docs.microsoft.com/windows/apps/develop/title-bar?tabs=winui3#full-customization
u.ExtendsContentIntoTitleBar = true;
WindowHelpers.ForceTopBorder1PixelInsetOnWindows10(WindowNative.GetWindowHandle(u));
u.SetTitleBar(AppTitleBar);
}
}
private void ShellPage_Loaded(object sender, RoutedEventArgs e)
{
SetTitleBar();
// Select the first module by default
if (navigationView.MenuItems.Count > 0)
{
navigationView.SelectedItem = navigationView.MenuItems[0];
}
}
private void NavigationView_DisplayModeChanged(NavigationView sender, NavigationViewDisplayModeChangedEventArgs args)
{
if (args.DisplayMode == NavigationViewDisplayMode.Compact || args.DisplayMode == NavigationViewDisplayMode.Minimal)
{
PaneToggleBtn.Visibility = Visibility.Visible;
AppTitleBar.Margin = new Thickness(48, 0, 0, 0);
AppTitleBarText.Margin = new Thickness(12, 0, 0, 0);
TitleBarIcon.Margin = new Thickness(0, 0, 8, 0); // Workaround, see XAML comment
AppTitleBar.IsPaneToggleButtonVisible = true;
}
else
{
PaneToggleBtn.Visibility = Visibility.Collapsed;
AppTitleBar.Margin = new Thickness(16, 0, 0, 0);
AppTitleBarText.Margin = new Thickness(16, 0, 0, 0);
TitleBarIcon.Margin = new Thickness(16, 0, 0, 0); // Workaround, see XAML comment
AppTitleBar.IsPaneToggleButtonVisible = false;
}
}
private void PaneToggleBtn_Click(object sender, RoutedEventArgs e)
private void TitleBar_PaneButtonClick(TitleBar sender, object args)
{
navigationView.IsPaneOpen = !navigationView.IsPaneOpen;
}
private void WhatIsNewItem_Tapped(object sender, TappedRoutedEventArgs e)
{
OpenScoobeWindow();
}
}
}

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