Compare commits

...

11 Commits

Author SHA1 Message Date
Leilei Zhang
b94bf4fdae update value 2025-12-05 22:07:22 +08:00
Leilei Zhang
2d18303f0a Merge branch 'main' of https://github.com/microsoft/PowerToys into leilzh/fixupdate 2025-12-05 21:44:06 +08:00
Niels Laute
cdf66a70e9 [AdvancedPaste] Fix outdated string in Settings (#44099)
Updating and removing outdated strings.

Updated string:

<img width="1565" height="323" alt="image"
src="https://github.com/user-attachments/assets/3dcad3b9-7ba9-4d87-ab36-405a8e1705db"
/>
2025-12-05 21:33:59 +08:00
Valentin Arthur Thomas
9dcddfd4b8 Quotation mark (#30481)
<!-- 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)?
-->
## Add Quotation mark
Add local quotation based on ~~VK_OEM_7(0xDE)~~ VK_OEM_COMMA(0xBC) key.
Not all quotes have been added, only `‟ „ ” « » ‚ , ‘ ’ › ‹ '「 」 《 》 『
』〈 〉″ ‴ ⁗`
Why not added :
- ` ⹂ ⌜ ⌝ ❛ ❜ ❝ ❞ 🙶 🙷 🙸 ' 「 」 ` its redundant and would make too much
and not readable.
- ` ﹁ ﹂ ﹃ ﹄ `  I did not put them because there use for horizontal text


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

- [x] Closes: https://github.com/microsoft/PowerToys/issues/29371
https://github.com/microsoft/PowerToys/issues/24832
- [ ] **Communication:** I've discussed this with core contributors
already. If 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 (None)
- [ ] [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 -->
This PR is currently a draft, I still need to know if adding
language-related keyboard management is a good idea or specifying the
use of a gobal key to make it easier to manage all the keyboards in one.
Some languages ​​can use different keyboards, I think this would become
problematic if the keyboard does not match the key used by default.
However, using a universal key can also pose an issue to finding the
key. that remains to be discussed
<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2025-12-05 11:57:01 +01:00
Copilot
503bcbdf2d Restore missing "Quick access" menu item in tray icon context menu (#42676)
## Summary
Restores the "Quick access" menu item that was accidentally removed from
the PowerToys tray icon context menu.

## Issue
Fixes #[issue_number]

The "Quick access" menu item was missing from the tray icon's
right-click context menu, preventing users from accessing this feature
via the tray menu.

**Expected menu:**

![Expected
menu](https://github.com/user-attachments/assets/805b1436-5a08-42e7-a34d-b9848fd9a235)

**Actual menu (before this fix):**

![Actual
menu](https://github.com/user-attachments/assets/7584035d-e893-4f73-acc3-84d789e31e81)

## Changes
- Added the missing `MENUITEM "Quick access\tLeft-click",
ID_QUICK_ACCESS_MENU_COMMAND` entry as the first menu item in the
`ID_TRAY_MENU` definition in `src/runner/runner.base.rc`

## Details
The menu item was accidentally removed in commit
f5797a065a. This PR restores it to its
correct position as the first item in the tray menu.

All supporting code was already in place:
- The resource ID `ID_QUICK_ACCESS_MENU_COMMAND` (40006) is defined in
`resource.base.h`
- The resource string `QUICK_ACCESS_MENU_TEXT` is defined in
`Resources.resx`
- The menu command handler in `tray_icon.cpp` opens the Quick Access
flyout window
- The localization code updates the menu text at runtime

## Testing
-  Verified the menu item syntax is correct and matches existing
patterns
-  Confirmed all supporting resource IDs and handler code exist
-  CI build verification pending

After this fix, the tray menu will correctly display:
1. Quick access (Left-click)
2. Settings (Double-click)
3. Documentation
4. Report Bug
5. Close

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



<details>

<summary>Original prompt</summary>

> 
> ----
> 
> *This section details on the original issue you should resolve*
> 
> <issue_title>Quick Access missing in tray menu</issue_title>
> <issue_description>### Microsoft PowerToys version
> 
> 0.95.0
> 
> ### Installation method
> 
> PowerToys auto-update
> 
> ### Area(s) with issue?
> 
> System tray interaction
> 
> ### Steps to reproduce
> 
> 1. Right click the tray icon
> 2. Look at the list of items which can be selected
> 3. Notice that the "Quick access" is missing
> 
> Expected menu ("Exit" is now "Close" I took this image from initial
implementation):
> 
> <img width="234" height="127" alt="Image"
src="https://github.com/user-attachments/assets/805b1436-5a08-42e7-a34d-b9848fd9a235"
/>
> 
> Actual menu right now:
> 
> <img width="239" height="105" alt="Image"
src="https://github.com/user-attachments/assets/7584035d-e893-4f73-acc3-84d789e31e81"
/>
> 
> ### ✔️ Expected Behavior
> 
> The "Quick access" menu item should be there
> 
> ###  Actual Behavior
> 
> The menu item is missing
> 
> ### Additional Information
> 
> Windows 10 Pro 22H2 19045.6332
> 
> (Also it is the same on my other computers)
> 
> ### Other Software
> 
> _No response_</issue_description>
> 
> <agent_instructions>restore
> 
> MENUITEM "Quick access\tLeft-click", ID_QUICK_ACCESS_MENU_COMMAND
> 
> as first menu item of ID_TRAY_MENU MENU in
src/runner/runner.base.rc</agent_instructions>
> 
> ## Comments on the Issue (you are @copilot in this section)
> 
> <comments>
> <comment_new><author>@niels9001</author><body>
> @davidegiacometti is this a regression from the PR where we updated
the string names?</body></comment_new>
> <comment_new><author>@davidegiacometti</author><body>
> Hi @niels9001 
> I just realized that the menu was missing in
https://github.com/microsoft/PowerToys/pull/40714 screenshots, but the
regression was caused by
f5797a065a.
> Unfortunately, many of the `.rc` files in the PT codebase have
UTF16-LE encoding and GitHub doesn't show diff.</body></comment_new>
> </comments>
> 


</details>

Fixes microsoft/PowerToys#42618

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

💬 Share your feedback on Copilot coding agent for the chance to win a
$200 gift card! Click
[here](https://survey3.medallia.com/?EAHeSx-AP01bZqG0Ld9QLQ) to start
the survey.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: davidegiacometti <25966642+davidegiacometti@users.noreply.github.com>
2025-12-05 07:33:37 +01:00
Jiří Polášek
52f2561937 CmdPal: Find app for WinGet package (#43943)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request

This PR introduces a bit of dark magic to resolve the correct installed
app for a given WinGet package:

- Packaged apps: matched using their package family name.
- Everything else: matched using the product code (GUID) and heuristic
registry lookup.
- The registry rarely stores the executable path directly, so the logic
compares install locations with known apps.
  - It attempts to pick the best candidate while avoiding uninstallers.
  - It’s not science — let’s call it `#666666` magic.
- MSI API support was removed because it's too slow for this scenario.
- If no reliable match is found, the command is skipped for now. The
future plan is to redirect the user to the list of installed apps and
search by display name, but that needs some supporting infrastructure
first.
- The command order for WinGet list entries was updated: **Install /
Uninstall** is now the primary action, ensuring a stable UI since this
command is always available.


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

- [x] Closes: #43671
<!-- - [ ] 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
2025-12-03 10:16:25 -06:00
Dustin L. Howett
dc30f3fd8e build: move main and setup to SLNX (#43478)
Closes #37100

This does not migrate the rest of the solutions (why do we have so
many?)

Not migrated:

- TemplateCmdPalExtension.sln
- FancyZonesEditor.sln
- BugReportTool.sln
- CleanUp_tool.sln
- FancyZones_DrawLayoutTest.sln
- FancyZones_zonable_tester.sln
- FancyZone_HitTest.sln
- MonitorReportTool.sln
- PowerToyTemplate.sln
- StylesReportTool.sln

---------

Co-authored-by: vanzue <vanzue@outlook.com>
2025-12-03 17:59:46 +08:00
Jessica Dene Earley-Cha
8f9a2c32cc add missing powertoys events (#44016)
## Summary of the Pull Request
This added missing telemetry events from modules that were not listed in
DATA_AND_PRIVACY

## 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
2025-12-02 09:59:57 -08:00
moooyo
bcd1583bb7 [AOT] Refactor SettingsLib/SettingsUI for Native AOT compatibility (#42644)
<!-- 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

Key Changes:

1. Settings.UI.Library:
- Added SettingsSerializationContext.cs with comprehensive
JsonSerializable attributes for all settings types
- Updated BasePTModuleSettings.ToJsonString() to use AOT-compatible
serialization
- Updated SettingsUtils.GetFile<T>() to use AOT-compatible
deserialization
- Modified all ToString() methods in Properties classes to use
SettingsSerializationContext
- Converted struct fields to properties in SunTimes and
MouseWithoutBordersProperties for serialization compatibility

2. Settings.UI:
- Fixed namespace alias in SourceGenerationContextContext.cs to avoid
conflicts

For any future developers who discover incorrect settings resolution,
please follow up my changes to add your setting type into
JsonSerilizerContext.



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

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

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

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

Co-authored-by: Yu Leng <yuleng@microsoft.com>
2025-12-02 16:31:02 +08:00
Leilei Zhang
abf5e5bf8d fix build error 2025-11-28 14:43:03 +08:00
Leilei Zhang
e1e74b8fab fix update 2025-11-27 21:35:40 +08:00
83 changed files with 2215 additions and 3709 deletions

View File

@@ -773,6 +773,7 @@ INITGUID
INITTOLOGFONTSTRUCT
INLINEPREFIX
inlines
Inno
INPC
inproc
INPUTHARDWARE
@@ -1629,6 +1630,7 @@ SKIPOWNPROCESS
sku
SLGP
sln
slnx
SMALLICON
smartphone
smileys
@@ -1847,6 +1849,7 @@ UNCPRIORITY
UNDNAME
UNICODETEXT
unins
Uninstaller
uninstalls
Uniquifies
unitconverter

View File

@@ -192,14 +192,14 @@ jobs:
displayName: Verify XAML formatting
- pwsh: |-
& '.pipelines/verifyNugetPackages.ps1' -solution '$(build.sourcesdirectory)\PowerToys.sln'
displayName: Verify Nuget package versions for PowerToys.sln
& '.pipelines/verifyNugetPackages.ps1' -solution '$(build.sourcesdirectory)\PowerToys.slnx'
displayName: Verify Nuget package versions for PowerToys.slnx
- pwsh: |-
& '.pipelines/verifyArm64Configuration.ps1' -solution '$(build.sourcesdirectory)\PowerToys.sln'
& '.pipelines/verifyArm64Configuration.ps1' -solution '$(build.sourcesdirectory)\PowerToys.slnx'
& '.pipelines/verifyArm64Configuration.ps1' -solution '$(build.sourcesdirectory)\tools\BugReportTool\BugReportTool.sln'
& '.pipelines/verifyArm64Configuration.ps1' -solution '$(build.sourcesdirectory)\tools\StylesReportTool\StylesReportTool.sln'
& '.pipelines/verifyArm64Configuration.ps1' -solution '$(build.sourcesdirectory)\installer\PowerToysSetup.sln'
& '.pipelines/verifyArm64Configuration.ps1' -solution '$(build.sourcesdirectory)\installer\PowerToysSetup.slnx'
displayName: Verify ARM64 configurations
- ${{ if eq(parameters.enablePackageCaching, true) }}:
@@ -252,7 +252,7 @@ jobs:
${{ else }}:
displayName: Build PowerToys main project
inputs:
solution: 'PowerToys.sln'
solution: 'PowerToys.slnx'
vsVersion: 17.0
msbuildArgs: >-
-restore -graph
@@ -275,7 +275,7 @@ jobs:
displayName: Generate DSC artifacts for ARM64
condition: and(succeeded(), eq(variables['BuildPlatform'], 'arm64'))
inputs:
solution: PowerToys.sln
solution: PowerToys.slnx
vsVersion: 17.0
msbuildArgs: >-
-restore

View File

@@ -74,7 +74,7 @@ jobs:
command: restore
feedsToUse: config
configPath: nuget.config
restoreSolution: PowerToys.sln
restoreSolution: PowerToys.slnx
restoreDirectory: '$(Build.SourcesDirectory)\packages'
# Build all UI test projects if no specific modules are specified
@@ -129,4 +129,4 @@ jobs:
- publish: $(JobOutputDirectory)
artifact: $(JobOutputArtifactName)
displayName: Publish UI Test artifacts
condition: always()
condition: always()

View File

@@ -35,7 +35,7 @@ steps:
- task: VSBuild@1
displayName: Build Shared Support DLLs
inputs:
solution: "**/installer/PowerToysSetup.sln"
solution: "**/installer/PowerToysSetup.slnx"
vsVersion: 17.0
msbuildArgs: >-
/t:PowerToysSetupCustomActionsVNext;SilentFilesInUseBAFunction
@@ -74,7 +74,7 @@ steps:
- task: VSBuild@1
displayName: 💻 Build VNext MSI
inputs:
solution: "**/installer/PowerToysSetup.sln"
solution: "**/installer/PowerToysSetup.slnx"
vsVersion: 17.0
msbuildArgs: >-
-restore
@@ -91,7 +91,7 @@ steps:
- task: VSBuild@1
displayName: 👤 Build VNext MSI
inputs:
solution: "**/installer/PowerToysSetup.sln"
solution: "**/installer/PowerToysSetup.slnx"
vsVersion: 17.0
msbuildArgs: >-
/t:PowerToysInstallerVNext
@@ -142,7 +142,7 @@ steps:
- task: VSBuild@1
displayName: 💻 Build VNext Bootstrapper
inputs:
solution: "**/installer/PowerToysSetup.sln"
solution: "**/installer/PowerToysSetup.slnx"
vsVersion: 17.0
msbuildArgs: >-
-restore
@@ -159,7 +159,7 @@ steps:
- task: VSBuild@1
displayName: 👤 Build VNext Bootstrapper
inputs:
solution: "**/installer/PowerToysSetup.sln"
solution: "**/installer/PowerToysSetup.slnx"
vsVersion: 17.0
msbuildArgs: >-
/t:PowerToysBootstrapperVNext

View File

@@ -54,4 +54,13 @@ steps:
feedsToUse: 'config'
nugetConfigPath: '$(build.sourcesdirectory)\nuget.config'
restoreSolution: '$(build.sourcesdirectory)\**\*.sln'
includeNuGetOrg: false
includeNuGetOrg: false
- task: NuGetCommand@2
displayName: 'Restore NuGet packages (slnx)'
inputs:
command: 'restore'
feedsToUse: 'config'
nugetConfigPath: '$(build.sourcesdirectory)\nuget.config'
restoreSolution: '$(build.sourcesdirectory)\**\*.slnx'
includeNuGetOrg: false

View File

@@ -243,6 +243,10 @@ _If you want to find diagnostic data events in the source code, these two links
<th>Event Name</th>
<th>Description</th>
</tr>
<tr>
<td>Microsoft.PowerToys.CmdNotFound_EnableCmdNotFound</td>
<td>Triggered when Command Not Found is enabled or disabled.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.CmdNotFoundInstallEvent</td>
<td>Triggered when a Command Not Found is installed.</td>
@@ -257,6 +261,62 @@ _If you want to find diagnostic data events in the source code, these two links
</tr>
</table>
### Command Palette
<table style="width:100%">
<tr>
<th>Event Name</th>
<th>Description</th>
</tr>
<tr>
<td>Microsoft.PowerToys.CmdPal_BeginInvoke</td>
<td>Triggered when the Command Palette is launched by the user.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.CmdPal_ColdLaunch</td>
<td>Occurs when Command Palette starts for the first time (cold start).</td>
</tr>
<tr>
<td>Microsoft.PowerToys.CmdPal_OpenPage</td>
<td>Triggered when a page is opened within the Command Palette, tracking navigation depth.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.CmdPal_OpenUri</td>
<td>Occurs when a URI is opened through the Command Palette, including whether it's a web URL.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.CmdPal_ReactivateInstance</td>
<td>Triggered when an existing Command Palette instance is reactivated.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.CmdPal_RunCommand</td>
<td>Logs when a command is executed through the Command Palette, including admin elevation status.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.CmdPal_RunQuery</td>
<td>Triggered when a search query is performed, including result count and duration.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.CmdPalDismissedOnEsc</td>
<td>Occurs when the Command Palette is dismissed by pressing the Escape key.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.CmdPalDismissedOnLostFocus</td>
<td>Triggered when the Command Palette is dismissed due to losing focus.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.CmdPalHotkeySummoned</td>
<td>Logs when the Command Palette is summoned via hotkey, distinguishing between global and context-specific hotkeys.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.CmdPalInvokeResult</td>
<td>Records the result type of a Command Palette invocation.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.CmdPalProcessStarted</td>
<td>Triggered when the Command Palette process is started.</td>
</tr>
</table>
### Crop And Lock
<table style="width:100%">
<tr>
@@ -735,6 +795,10 @@ _If you want to find diagnostic data events in the source code, these two links
<th>Event Name</th>
<th>Description</th>
</tr>
<tr>
<td>Microsoft.PowerToys.NewPlus_ChangedTemplateLocation</td>
<td>Triggered when the template folder location is changed.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.NewPlus_EventCopyTemplate</td>
<td>Triggered when an item from New+ is created (copied to the current directory).</td>
@@ -743,6 +807,10 @@ _If you want to find diagnostic data events in the source code, these two links
<td>Microsoft.PowerToys.NewPlus_EventCopyTemplateResult</td>
<td>Logs the success of item creation (copying).</td>
</tr>
<tr>
<td>Microsoft.PowerToys.NewPlus_EventOpenTemplates</td>
<td>Triggered when the templates folder is opened.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.NewPlus_EventShowTemplateItems</td>
<td>Triggered when the New+ context menu flyout is displayed.</td>
@@ -928,12 +996,8 @@ _If you want to find diagnostic data events in the source code, these two links
<th>Description</th>
</tr>
<tr>
<td>Microsoft.PowerToys.ShortcutGuide_EnableGuide</td>
<td>Triggered when Shortcut Guide is enabled.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.ShortcutGuide_HideGuide</td>
<td>Occurs when Shortcut Guide is hidden from view.</td>
<td>Microsoft.PowerToys.ShortcutGuide_GuideSession</td>
<td>Logs a Shortcut Guide session including duration and how it was closed.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.ShortcutGuide_Settings</td>

File diff suppressed because it is too large Load Diff

1041
PowerToys.slnx Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -134,7 +134,7 @@ If you prefer, you can alternatively build prerequisite projects for the install
#### Locally compiling the installer
1. Open `installer\PowerToysSetup.sln`
1. Open `installer\PowerToysSetup.slnx`
1. In Visual Studio, in the `Solutions Configuration` drop-down menu select `Release`
1. From the `Build` menu choose `Build Solution`.
@@ -144,9 +144,9 @@ To build the installer from the command line, run `Developer Command Prompt for
```
git clean -xfd -e *exe -- .\installer\
MSBuild -t:restore .\installer\PowerToysSetup.sln -p:RestorePackagesConfig=true /p:Platform="x64" /p:Configuration=Release
MSBuild -t:Restore -m .\installer\PowerToysSetup.sln /t:PowerToysInstallerVNext /p:Configuration=Release /p:Platform="x64"
MSBuild -t:Restore -m .\installer\PowerToysSetup.sln /t:PowerToysBootstrapperVNext /p:Configuration=Release /p:Platform="x64"
MSBuild -t:restore .\installer\PowerToysSetup.slnx -p:RestorePackagesConfig=true /p:Platform="x64" /p:Configuration=Release
MSBuild -t:Restore -m .\installer\PowerToysSetup.slnx /t:PowerToysInstallerVNext /p:Configuration=Release /p:Platform="x64"
MSBuild -t:Restore -m .\installer\PowerToysSetup.slnx /t:PowerToysBootstrapperVNext /p:Configuration=Release /p:Platform="x64"
```
### Supported arguments for the .EXE Bootstrapper installer

View File

@@ -19,7 +19,7 @@ You can build the entire solution from the command line, which is sometimes fast
2. Navigate to the repository root directory
3. Run the following command(don't forget to set the correct platform):
```pwsh
msbuild -restore -p:RestorePackagesConfig=true -p:Platform=ARM64 -m PowerToys.sln /tl /p:NuGetInteractive="true"
msbuild -restore -p:RestorePackagesConfig=true -p:Platform=ARM64 -m PowerToys.slnx /tl /p:NuGetInteractive="true"
```
4. This process should complete in approximately 13-14 minutes for a full build

View File

@@ -42,10 +42,10 @@ Or reach out to "tools\build\BUILD-GUIDELINES.md"
### Sample plain msbuild command
```powershell
# Restore:
msbuild powertoys.sln -t:restore -p:configuration=debug -p:platform=x64 -m
msbuild powertoys.slnx -t:restore -p:configuration=debug -p:platform=x64 -m
# Build powertoys sln
msbuild powertoys.sln -p:configuration=debug -p:platform=x64 -m
# Build powertoys slnx
msbuild powertoys.slnx -p:configuration=debug -p:platform=x64 -m
# dotnet project
msbuild src\settings-ui\Settings.UI\PowerToys.Settings.csproj -p:Platform=x64 -p:Configuration=Debug -m
@@ -122,7 +122,7 @@ Similar for attach to managed code.
| Task | Command / Action | Notes |
|------|------------------|-------|
| Clean | `git clean -xdf` (careful) or `msbuild /t:Clean PowerToys.sln` | Deep clean removes packages & build outputs |
| Clean | `git clean -xdf` (careful) or `msbuild /t:Clean PowerToys.slnx` | Deep clean removes packages & build outputs |
| Rebuild single project | `msbuild path\to\proj.vcxproj /t:Rebuild -p:Platform=x64 -p:Configuration=Debug` | Faster than whole solution |
| Generate installer (rare in inner loop) | See `tools\build\build-installer.ps1` | Usually not needed for local debug |
| Resource conversion errors | Re-run restore + build | Triggers custom PowerShell targets |
| Resource conversion errors | Re-run restore + build | Triggers custom PowerShell targets |

View File

@@ -12,7 +12,7 @@
- Exit PowerToys if it's running.
- Open `PowerToys.sln` in Visual Studio and build the solution.
- Open `PowerToys.slnx` in Visual Studio and build the solution.
- Run tests in the Test Explorer (`Test > Test Explorer` or `Ctrl+E, T`).

View File

@@ -86,7 +86,7 @@ The module provides a user interface for configuring settings in the PowerToys S
### Building and Testing
1. Clone the repository: `git clone https://github.com/microsoft/PowerToys.git`
2. Open PowerToys.sln in Visual Studio
2. Open PowerToys.slnx in Visual Studio
3. Select the Release configuration and build the solution
4. Run PowerToys.exe from the output directory to test the module

View File

@@ -161,7 +161,7 @@ FancyZones is divided into several projects:
```
git clone https://github.com/microsoft/PowerToys.git
```
2. Open `PowerToys.sln` in Visual Studio
2. Open `PowerToys.slnx` in Visual Studio
3. Select the Release configuration and build the solution
4. If you encounter build errors, try deleting the x64 output folder and rebuild
@@ -244,7 +244,7 @@ UI tests are implemented using [Windows Application Driver](https://github.com/m
- Exit PowerToys if it's running
- Run WinAppDriver.exe from the installation directory. Skip this step if installed in the default directory (`C:\Program Files (x86)\Windows Application Driver`); in this case, it'll be launched automatically during tests.
- Open `PowerToys.sln` in Visual Studio and build the solution.
- Open `PowerToys.slnx` in Visual Studio and build the solution.
- Run tests in the Test Explorer (`Test > Test Explorer` or `Ctrl+E, T`).
>Note: notifications or other application windows, that are shown above the window under test, can disrupt the testing process.

View File

@@ -11,7 +11,7 @@ Keyboard Manager consists of two main components:
## Development Environment Setup
1. Clone the PowerToys repository
2. Open `PowerToys.sln` in Visual Studio
2. Open `PowerToys.slnx` in Visual Studio
3. Ensure all NuGet packages are restored
4. Build the entire solution in Debug configuration
@@ -91,4 +91,4 @@ If you encounter issues with multiple instances, check the mutex logic in `Keybo
To debug both the Editor and Engine:
1. Launch the Engine first in debug mode
2. Attach the debugger to the Editor process when it starts
2. Attach the debugger to the Editor process when it starts

View File

@@ -92,7 +92,7 @@ The modules settings are exposed in the PowerToys Settings UI. Options includ
3. Build the solution:
```sh
msbuild -restore -p:RestorePackagesConfig=true -p:Platform=ARM64 -m PowerToys.sln
msbuild -restore -p:RestorePackagesConfig=true -p:Platform=ARM64 -m PowerToys.slnx
```
> Note: This may take some time.

View File

@@ -53,7 +53,7 @@ The Screen Ruler module consists of several components:
### Building
1. Open PowerToys.sln in Visual Studio
1. Open PowerToys.slnx in Visual Studio
2. In the Solutions Configuration drop-down menu, select Release or Debug
3. From the Build menu, choose Build Solution
4. The executable app for Screen Ruler is named PowerToys.MeasureToolUI.exe

View File

@@ -19,7 +19,7 @@ Shortcut Guide is a PowerToy that displays an overlay of available keyboard shor
## Build and Debug Instructions
### Build
1. Open PowerToys.sln in Visual Studio
1. Open PowerToys.slnx in Visual Studio
2. Select Release or Debug in the Solutions Configuration drop-down menu
3. From the Build menu, choose Build Solution
4. The executable is named PowerToys.ShortcutGuide.exe

View File

@@ -80,7 +80,7 @@ Once you've discussed your proposed feature/fix/etc. with a team member, and an
### Install Visual Studio dependencies
1. Open the `PowerToys.sln` file.
1. Open the `PowerToys.slnx` file.
1. If you see a dialog that says `install extra components` in the solution explorer pane, click `install`
### Get Submodules to compile
@@ -93,7 +93,7 @@ We have submodules that need to be initialized before you can compile most parts
### Compiling Source Code
- Open `PowerToys.sln` in Visual Studio.
- Open `PowerToys.slnx` in Visual Studio.
- In the `Solutions Configuration` drop-down menu select `Release` or `Debug`.
- From the `Build` menu choose `Build Solution`, or press <kbd>Control</kbd>+<kbd>Shift</kbd>+<kbd>b</kbd> on your keyboard.
- The build process may take several minutes depending on your computer's performance. Once it completes, the PowerToys binaries will be in your repo under `x64\Release\`.
@@ -107,10 +107,10 @@ Our installer is two parts, an EXE and an MSI. The EXE (Bootstrapper) contains
The installer can only be compiled in `Release` mode; steps 1 and 2 must be performed before the MSI can be compiled.
1. Compile `PowerToys.sln`. Instructions are listed above.
1. Compile `PowerToys.slnx`. Instructions are listed above.
1. Compile `BugReportTool.sln` tool. Path from root: `tools\BugReportTool\BugReportTool.sln` (details listed below)
1. Compile `StylesReportTool.sln` tool. Path from root: `tools\StylesReportTool\StylesReportTool.sln` (details listed below)
1. Compile `PowerToysSetup.sln` Path from root: `installer\PowerToysSetup.sln` (details listed below)
1. Compile `PowerToysSetup.slnx` Path from root: `installer\PowerToysSetup.slnx` (details listed below)
See [Installer](core/installer.md) for more details on building and debugging the installer.

View File

@@ -1,96 +0,0 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.1.32414.318
MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "spdlog", "..\src\logging\logging.vcxproj", "{7E1E3F13-2BD6-3F75-A6A7-873A2B55C60F}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "logger", "..\src\common\logger\logger.vcxproj", "{D9B8FC84-322A-4F9F-BBB9-20915C47DDFD}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Version", "..\src\common\version\version.vcxproj", "{CC6E41AC-8174-4E8A-8D22-85DD7F4851DF}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "EtwTrace", "..\src\common\Telemetry\EtwTrace\EtwTrace.vcxproj", "{8F021B46-362B-485C-BFBA-CCF83E820CBD}"
EndProject
Project("{930C7802-8A8C-48F9-8165-68863BCCD9DD}") = "PowerToysInstallerVNext", "PowerToysSetupVNext\PowerToysInstallerVNext.wixproj", "{B6E94700-DF38-41F6-A3FD-18B69674AB1E}"
EndProject
Project("{930C7802-8A8C-48F9-8165-68863BCCD9DD}") = "PowerToysBootstrapperVNext", "PowerToysSetupVNext\PowerToysBootstrapperVNext.wixproj", "{DA4E9744-80BE-424C-B0F5-AFD8757DB575}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PowerToysSetupCustomActionsVNext", "PowerToysSetupCustomActionsVNext\PowerToysSetupCustomActionsVNext.vcxproj", "{B3A354B0-1E54-4B55-A962-FB5AF9330C19}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SilentFilesInUseBAFunction", "PowerToysSetupVNext\SilentFilesInUseBA\SilentFilesInUseBAFunction.vcxproj", "{F8B9F842-F5C3-4A2D-8C85-7F8B9E2B4F1D}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|ARM64 = Debug|ARM64
Debug|x64 = Debug|x64
Release|ARM64 = Release|ARM64
Release|x64 = Release|x64
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{7E1E3F13-2BD6-3F75-A6A7-873A2B55C60F}.Debug|ARM64.ActiveCfg = Debug|ARM64
{7E1E3F13-2BD6-3F75-A6A7-873A2B55C60F}.Debug|x64.ActiveCfg = Debug|x64
{7E1E3F13-2BD6-3F75-A6A7-873A2B55C60F}.Debug|x64.Build.0 = Debug|x64
{7E1E3F13-2BD6-3F75-A6A7-873A2B55C60F}.Release|ARM64.ActiveCfg = Release|ARM64
{7E1E3F13-2BD6-3F75-A6A7-873A2B55C60F}.Release|ARM64.Build.0 = Release|ARM64
{7E1E3F13-2BD6-3F75-A6A7-873A2B55C60F}.Release|x64.ActiveCfg = Release|x64
{7E1E3F13-2BD6-3F75-A6A7-873A2B55C60F}.Release|x64.Build.0 = Release|x64
{D9B8FC84-322A-4F9F-BBB9-20915C47DDFD}.Debug|ARM64.ActiveCfg = Debug|ARM64
{D9B8FC84-322A-4F9F-BBB9-20915C47DDFD}.Debug|x64.ActiveCfg = Debug|x64
{D9B8FC84-322A-4F9F-BBB9-20915C47DDFD}.Debug|x64.Build.0 = Debug|x64
{D9B8FC84-322A-4F9F-BBB9-20915C47DDFD}.Release|ARM64.ActiveCfg = Release|ARM64
{D9B8FC84-322A-4F9F-BBB9-20915C47DDFD}.Release|ARM64.Build.0 = Release|ARM64
{D9B8FC84-322A-4F9F-BBB9-20915C47DDFD}.Release|x64.ActiveCfg = Release|x64
{D9B8FC84-322A-4F9F-BBB9-20915C47DDFD}.Release|x64.Build.0 = Release|x64
{CC6E41AC-8174-4E8A-8D22-85DD7F4851DF}.Debug|ARM64.ActiveCfg = Debug|ARM64
{CC6E41AC-8174-4E8A-8D22-85DD7F4851DF}.Debug|ARM64.Build.0 = Debug|ARM64
{CC6E41AC-8174-4E8A-8D22-85DD7F4851DF}.Debug|x64.ActiveCfg = Debug|x64
{CC6E41AC-8174-4E8A-8D22-85DD7F4851DF}.Debug|x64.Build.0 = Debug|x64
{CC6E41AC-8174-4E8A-8D22-85DD7F4851DF}.Release|ARM64.ActiveCfg = Release|ARM64
{CC6E41AC-8174-4E8A-8D22-85DD7F4851DF}.Release|ARM64.Build.0 = Release|ARM64
{CC6E41AC-8174-4E8A-8D22-85DD7F4851DF}.Release|x64.ActiveCfg = Release|x64
{CC6E41AC-8174-4E8A-8D22-85DD7F4851DF}.Release|x64.Build.0 = Release|x64
{8F021B46-362B-485C-BFBA-CCF83E820CBD}.Debug|ARM64.ActiveCfg = Debug|ARM64
{8F021B46-362B-485C-BFBA-CCF83E820CBD}.Debug|ARM64.Build.0 = Debug|ARM64
{8F021B46-362B-485C-BFBA-CCF83E820CBD}.Debug|x64.ActiveCfg = Debug|x64
{8F021B46-362B-485C-BFBA-CCF83E820CBD}.Debug|x64.Build.0 = Debug|x64
{8F021B46-362B-485C-BFBA-CCF83E820CBD}.Release|ARM64.ActiveCfg = Release|ARM64
{8F021B46-362B-485C-BFBA-CCF83E820CBD}.Release|ARM64.Build.0 = Release|ARM64
{8F021B46-362B-485C-BFBA-CCF83E820CBD}.Release|x64.ActiveCfg = Release|x64
{8F021B46-362B-485C-BFBA-CCF83E820CBD}.Release|x64.Build.0 = Release|x64
{B6E94700-DF38-41F6-A3FD-18B69674AB1E}.Debug|ARM64.ActiveCfg = Debug|ARM64
{B6E94700-DF38-41F6-A3FD-18B69674AB1E}.Debug|ARM64.Build.0 = Debug|ARM64
{B6E94700-DF38-41F6-A3FD-18B69674AB1E}.Debug|x64.ActiveCfg = Debug|x64
{B6E94700-DF38-41F6-A3FD-18B69674AB1E}.Debug|x64.Build.0 = Debug|x64
{B6E94700-DF38-41F6-A3FD-18B69674AB1E}.Release|ARM64.ActiveCfg = Release|ARM64
{B6E94700-DF38-41F6-A3FD-18B69674AB1E}.Release|ARM64.Build.0 = Release|ARM64
{B6E94700-DF38-41F6-A3FD-18B69674AB1E}.Release|x64.ActiveCfg = Release|x64
{B6E94700-DF38-41F6-A3FD-18B69674AB1E}.Release|x64.Build.0 = Release|x64
{DA4E9744-80BE-424C-B0F5-AFD8757DB575}.Debug|ARM64.ActiveCfg = Debug|ARM64
{DA4E9744-80BE-424C-B0F5-AFD8757DB575}.Debug|ARM64.Build.0 = Debug|ARM64
{DA4E9744-80BE-424C-B0F5-AFD8757DB575}.Debug|x64.ActiveCfg = Debug|x64
{DA4E9744-80BE-424C-B0F5-AFD8757DB575}.Debug|x64.Build.0 = Debug|x64
{DA4E9744-80BE-424C-B0F5-AFD8757DB575}.Release|ARM64.ActiveCfg = Release|ARM64
{DA4E9744-80BE-424C-B0F5-AFD8757DB575}.Release|ARM64.Build.0 = Release|ARM64
{DA4E9744-80BE-424C-B0F5-AFD8757DB575}.Release|x64.ActiveCfg = Release|x64
{DA4E9744-80BE-424C-B0F5-AFD8757DB575}.Release|x64.Build.0 = Release|x64
{B3A354B0-1E54-4B55-A962-FB5AF9330C19}.Debug|ARM64.ActiveCfg = Debug|ARM64
{B3A354B0-1E54-4B55-A962-FB5AF9330C19}.Debug|x64.ActiveCfg = Debug|x64
{B3A354B0-1E54-4B55-A962-FB5AF9330C19}.Debug|x64.Build.0 = Debug|x64
{B3A354B0-1E54-4B55-A962-FB5AF9330C19}.Release|ARM64.ActiveCfg = Release|ARM64
{B3A354B0-1E54-4B55-A962-FB5AF9330C19}.Release|ARM64.Build.0 = Release|ARM64
{B3A354B0-1E54-4B55-A962-FB5AF9330C19}.Release|x64.ActiveCfg = Release|x64
{B3A354B0-1E54-4B55-A962-FB5AF9330C19}.Release|x64.Build.0 = Release|x64
{F8B9F842-F5C3-4A2D-8C85-7F8B9E2B4F1D}.Debug|ARM64.ActiveCfg = Debug|ARM64
{F8B9F842-F5C3-4A2D-8C85-7F8B9E2B4F1D}.Debug|x64.ActiveCfg = Debug|x64
{F8B9F842-F5C3-4A2D-8C85-7F8B9E2B4F1D}.Debug|x64.Build.0 = Debug|x64
{F8B9F842-F5C3-4A2D-8C85-7F8B9E2B4F1D}.Release|ARM64.ActiveCfg = Release|ARM64
{F8B9F842-F5C3-4A2D-8C85-7F8B9E2B4F1D}.Release|ARM64.Build.0 = Release|ARM64
{F8B9F842-F5C3-4A2D-8C85-7F8B9E2B4F1D}.Release|x64.ActiveCfg = Release|x64
{F8B9F842-F5C3-4A2D-8C85-7F8B9E2B4F1D}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {B7A3DA30-D443-40FF-AC51-988AD41E3962}
EndGlobalSection
EndGlobal

View File

@@ -0,0 +1,22 @@
<Solution>
<Configurations>
<Platform Name="ARM64" />
<Platform Name="x64" />
</Configurations>
<Project Path="../src/common/logger/logger.vcxproj" Id="d9b8fc84-322a-4f9f-bbb9-20915c47ddfd">
<Build Solution="Debug|ARM64" Project="false" />
</Project>
<Project Path="../src/common/Telemetry/EtwTrace/EtwTrace.vcxproj" Id="8f021b46-362b-485c-bfba-ccf83e820cbd" />
<Project Path="../src/common/version/version.vcxproj" Id="cc6e41ac-8174-4e8a-8d22-85dd7f4851df" />
<Project Path="../src/logging/logging.vcxproj" Id="7e1e3f13-2bd6-3f75-a6a7-873a2b55c60f">
<Build Solution="Debug|ARM64" Project="false" />
</Project>
<Project Path="PowerToysSetupCustomActionsVNext/PowerToysSetupCustomActionsVNext.vcxproj" Id="b3a354b0-1e54-4b55-a962-fb5af9330c19">
<Build Solution="Debug|ARM64" Project="false" />
</Project>
<Project Path="PowerToysSetupVNext/PowerToysBootstrapperVNext.wixproj" Type="b7dd6f7e-def8-4e67-b5b7-07ef123db6f0" />
<Project Path="PowerToysSetupVNext/PowerToysInstallerVNext.wixproj" Type="b7dd6f7e-def8-4e67-b5b7-07ef123db6f0" />
<Project Path="PowerToysSetupVNext/SilentFilesInUseBA/SilentFilesInUseBAFunction.vcxproj" Id="f8b9f842-f5c3-4a2d-8c85-7f8b9e2b4f1d">
<Build Solution="Debug|ARM64" Project="false" />
</Project>
</Solution>

View File

@@ -16,9 +16,54 @@
namespace registry
{
namespace detail
{
struct on_exit
{
std::function<void()> f;
on_exit(std::function<void()> f) :
f{ std::move(f) } {}
~on_exit() { f(); }
};
template<class... Ts>
struct overloaded : Ts...
{
using Ts::operator()...;
};
template<class... Ts>
overloaded(Ts...) -> overloaded<Ts...>;
inline const wchar_t* getScopeName(HKEY scope)
{
if (scope == HKEY_LOCAL_MACHINE)
{
return L"HKLM";
}
else if (scope == HKEY_CURRENT_USER)
{
return L"HKCU";
}
else if (scope == HKEY_CLASSES_ROOT)
{
return L"HKCR";
}
else
{
return L"HK??";
}
}
}
namespace install_scope
{
const wchar_t INSTALL_SCOPE_REG_KEY[] = L"Software\\Classes\\powertoys\\";
const wchar_t UNINSTALL_REG_KEY[] = L"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall";
// Bundle UpgradeCode from PowerToys.wxs (with braces as stored in registry)
const wchar_t BUNDLE_UPGRADE_CODE[] = L"{6341382D-C0A9-4238-9188-BE9607E3FAB2}";
enum class InstallScope
{
@@ -26,8 +71,67 @@ namespace registry
PerUser,
};
// Helper function to find PowerToys bundle in Windows Uninstall registry by BundleUpgradeCode
inline bool find_powertoys_bundle_in_uninstall_registry(HKEY rootKey)
{
HKEY uninstallKey{};
if (RegOpenKeyExW(rootKey, UNINSTALL_REG_KEY, 0, KEY_READ, &uninstallKey) != ERROR_SUCCESS)
{
return false;
}
detail::on_exit closeUninstallKey{ [uninstallKey] { RegCloseKey(uninstallKey); } };
DWORD index = 0;
wchar_t subKeyName[256];
// Enumerate all subkeys under Uninstall
while (RegEnumKeyW(uninstallKey, index++, subKeyName, 256) == ERROR_SUCCESS)
{
HKEY productKey{};
if (RegOpenKeyExW(uninstallKey, subKeyName, 0, KEY_READ, &productKey) != ERROR_SUCCESS)
{
continue;
}
detail::on_exit closeProductKey{ [productKey] { RegCloseKey(productKey); } };
// Check BundleUpgradeCode value (specific to WiX Bundle installations)
wchar_t bundleUpgradeCode[256]{};
DWORD bundleUpgradeCodeSize = sizeof(bundleUpgradeCode);
if (RegQueryValueExW(productKey, L"BundleUpgradeCode", nullptr, nullptr,
reinterpret_cast<LPBYTE>(bundleUpgradeCode), &bundleUpgradeCodeSize) == ERROR_SUCCESS)
{
if (_wcsicmp(bundleUpgradeCode, BUNDLE_UPGRADE_CODE) == 0)
{
return true;
}
}
}
return false;
}
inline const InstallScope get_current_install_scope()
{
// 1. Check HKCU Uninstall registry first (user-level bundle)
// Note: MSI components are always in HKLM regardless of install scope,
// but the Bundle entry will be in HKCU for per-user installations
if (find_powertoys_bundle_in_uninstall_registry(HKEY_CURRENT_USER))
{
Logger::info(L"Found user-level PowerToys bundle via BundleUpgradeCode in HKCU");
return InstallScope::PerUser;
}
// 2. Check HKLM Uninstall registry (machine-level bundle)
if (find_powertoys_bundle_in_uninstall_registry(HKEY_LOCAL_MACHINE))
{
Logger::info(L"Found machine-level PowerToys bundle via BundleUpgradeCode in HKLM");
return InstallScope::PerMachine;
}
// 3. Fallback to legacy custom registry key detection
Logger::info(L"PowerToys bundle not found in Uninstall registry, falling back to legacy detection");
// Open HKLM key
HKEY perMachineKey{};
if (RegOpenKeyExW(HKEY_LOCAL_MACHINE,
@@ -45,6 +149,7 @@ namespace registry
&perUserKey) != ERROR_SUCCESS)
{
// both keys are missing
Logger::warn(L"No PowerToys installation detected, defaulting to PerMachine");
return InstallScope::PerMachine;
}
else
@@ -96,47 +201,6 @@ namespace registry
template<class>
inline constexpr bool always_false_v = false;
namespace detail
{
struct on_exit
{
std::function<void()> f;
on_exit(std::function<void()> f) :
f{ std::move(f) } {}
~on_exit() { f(); }
};
template<class... Ts>
struct overloaded : Ts...
{
using Ts::operator()...;
};
template<class... Ts>
overloaded(Ts...) -> overloaded<Ts...>;
inline const wchar_t* getScopeName(HKEY scope)
{
if (scope == HKEY_LOCAL_MACHINE)
{
return L"HKLM";
}
else if (scope == HKEY_CURRENT_USER)
{
return L"HKCU";
}
else if (scope == HKEY_CLASSES_ROOT)
{
return L"HKCR";
}
else
{
return L"HK??";
}
}
}
struct ValueChange
{
using value_t = std::variant<DWORD, std::wstring>;

View File

@@ -135,8 +135,9 @@ public partial class App : Application
try
{
var winget = new WinGetExtensionCommandsProvider();
var callback = allApps.LookupApp;
winget.SetAllLookup(callback);
winget.SetAllLookup(
query => allApps.LookupAppByPackageFamilyName(query, requireSingleMatch: true),
query => allApps.LookupAppByProductCode(query, requireSingleMatch: true));
services.AddSingleton<ICommandProvider>(winget);
}
catch (Exception ex)

View File

@@ -58,7 +58,7 @@ public class AllAppsCommandProviderTests : AppsTestBase
var provider = new AllAppsCommandProvider(page);
// Act
var result = provider.LookupApp(string.Empty);
var result = provider.LookupAppByDisplayName(string.Empty);
// Assert
Assert.IsNotNull(result);
@@ -77,7 +77,7 @@ public class AllAppsCommandProviderTests : AppsTestBase
await WaitForPageInitializationAsync();
// Act
var result = provider.LookupApp("TestApp");
var result = provider.LookupAppByDisplayName("TestApp");
// Assert
Assert.IsNotNull(result);
@@ -97,7 +97,7 @@ public class AllAppsCommandProviderTests : AppsTestBase
await WaitForPageInitializationAsync();
// Act
var result = provider.LookupApp("NonExistentApp");
var result = provider.LookupAppByDisplayName("NonExistentApp");
// Assert
Assert.IsNull(result);

View File

@@ -4,6 +4,8 @@
using System;
using System.Collections.Generic;
using Microsoft.CmdPal.Ext.Apps.Helpers;
using Microsoft.CmdPal.Ext.Apps.Programs;
using Microsoft.CmdPal.Ext.Apps.Properties;
using Microsoft.CmdPal.Ext.Apps.State;
using Microsoft.CommandPalette.Extensions;
@@ -66,7 +68,71 @@ public partial class AllAppsCommandProvider : CommandProvider
public override ICommandItem[] TopLevelCommands() => [_listItem, .. _page.GetPinnedApps()];
public ICommandItem? LookupApp(string displayName)
public ICommandItem? LookupAppByPackageFamilyName(string packageFamilyName, bool requireSingleMatch)
{
if (string.IsNullOrEmpty(packageFamilyName))
{
return null;
}
var items = _page.GetItems();
List<ICommandItem> matches = [];
foreach (var item in items)
{
if (item is AppListItem appItem && string.Equals(packageFamilyName, appItem.App.PackageFamilyName, StringComparison.OrdinalIgnoreCase))
{
matches.Add(item);
if (!requireSingleMatch)
{
// Return early if we don't require uniqueness.
return item;
}
}
}
return requireSingleMatch && matches.Count == 1 ? matches[0] : null;
}
public ICommandItem? LookupAppByProductCode(string productCode, bool requireSingleMatch)
{
if (string.IsNullOrEmpty(productCode))
{
return null;
}
if (!UninstallRegistryAppLocator.TryGetInstallInfo(productCode, out _, out var candidates) || candidates.Count <= 0)
{
return null;
}
var items = _page.GetItems();
List<ICommandItem> matches = [];
foreach (var item in items)
{
if (item is not AppListItem appListItem || string.IsNullOrEmpty(appListItem.App.FullExecutablePath))
{
continue;
}
foreach (var candidate in candidates)
{
if (string.Equals(appListItem.App.FullExecutablePath, candidate, StringComparison.OrdinalIgnoreCase))
{
matches.Add(item);
if (!requireSingleMatch)
{
return item;
}
}
}
}
return requireSingleMatch && matches.Count == 1 ? matches[0] : null;
}
public ICommandItem? LookupAppByDisplayName(string displayName)
{
var items = _page.GetItems();

View File

@@ -29,6 +29,10 @@ public sealed class AppItem
public string AppIdentifier { get; set; } = string.Empty;
public string? PackageFamilyName { get; set; }
public string? FullExecutablePath { get; set; }
public AppItem()
{
}

View File

@@ -40,6 +40,8 @@ public sealed partial class AppListItem : ListItem
public string AppIdentifier => _app.AppIdentifier;
public AppItem App => _app;
public AppListItem(AppItem app, bool useThumbnails, bool isPinned)
{
Command = _appCommand = new AppCommand(app);
@@ -82,6 +84,12 @@ public sealed partial class AppListItem : ListItem
metadata.Add(new DetailsElement() { Key = "Path", Data = new DetailsLink() { Text = _app.ExePath } });
}
#if DEBUG
metadata.Add(new DetailsElement() { Key = "[DEBUG] AppIdentifier", Data = new DetailsLink() { Text = _app.AppIdentifier } });
metadata.Add(new DetailsElement() { Key = "[DEBUG] ExePath", Data = new DetailsLink() { Text = _app.ExePath } });
metadata.Add(new DetailsElement() { Key = "[DEBUG] IcoPath", Data = new DetailsLink() { Text = _app.IcoPath } });
#endif
// Icon
IconInfo? heroImage = null;
if (_app.IsPackaged)

View File

@@ -0,0 +1,205 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.Win32;
namespace Microsoft.CmdPal.Ext.Apps.Helpers;
internal static class UninstallRegistryAppLocator
{
private static readonly string[] UninstallBaseKeys =
[
@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall",
@"SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall",
];
/// <summary>
/// Tries to find install directory and a list of plausible main EXEs from an uninstall key
/// (e.g. Inno Setup keys like "{guid}_is1").
/// <paramref name="exeCandidates"/> may be empty if we couldn't pick any safe EXEs.
/// </summary>
/// <returns>
/// Returns true if the uninstall key is found and an install directory is resolved.
/// </returns>
public static bool TryGetInstallInfo(
string uninstallKeyName,
out string? installDir,
out IReadOnlyList<string> exeCandidates,
string? expectedExeName = null)
{
installDir = null;
exeCandidates = [];
if (string.IsNullOrWhiteSpace(uninstallKeyName))
{
throw new ArgumentException("Key name must not be null or empty.", nameof(uninstallKeyName));
}
uninstallKeyName = uninstallKeyName.Trim();
foreach (var baseKeyPath in UninstallBaseKeys)
{
// HKLM
using (var key = Registry.LocalMachine.OpenSubKey($"{baseKeyPath}\\{uninstallKeyName}"))
{
if (TryFromUninstallKey(key, expectedExeName, out installDir, out exeCandidates))
{
return true;
}
}
// HKCU
using (var key = Registry.CurrentUser.OpenSubKey($"{baseKeyPath}\\{uninstallKeyName}"))
{
if (TryFromUninstallKey(key, expectedExeName, out installDir, out exeCandidates))
{
return true;
}
}
}
return false;
}
private static bool TryFromUninstallKey(
RegistryKey? key,
string? expectedExeName,
out string? installDir,
out IReadOnlyList<string> exeCandidates)
{
installDir = null;
exeCandidates = [];
if (key is null)
{
return false;
}
var location = (key.GetValue("InstallLocation") as string)?.Trim('"', ' ', '\t');
if (string.IsNullOrEmpty(location))
{
location = (key.GetValue("Inno Setup: App Path") as string)?.Trim('"', ' ', '\t');
}
if (string.IsNullOrEmpty(location))
{
var uninstall = key.GetValue("UninstallString") as string;
var uninsExe = ExtractFirstPath(uninstall);
if (!string.IsNullOrEmpty(uninsExe))
{
var dir = Path.GetDirectoryName(uninsExe);
if (!string.IsNullOrEmpty(dir) && Directory.Exists(dir))
{
location = dir;
}
}
}
if (string.IsNullOrEmpty(location) || !Directory.Exists(location))
{
return false;
}
installDir = location;
// Collect safe EXE candidates; may be empty if ambiguous or only uninstall exes exist.
exeCandidates = GetExeCandidates(location, expectedExeName);
return true;
}
private static IReadOnlyList<string> GetExeCandidates(string root, string? expectedExeName)
{
// Look at root and a "bin" subfolder (very common pattern)
var allExes = Directory.EnumerateFiles(root, "*.exe", SearchOption.TopDirectoryOnly)
.Concat(GetBinExes(root))
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToArray();
if (allExes.Length == 0)
{
return [];
}
var result = new List<string>();
// 1) Exact match on expected exe name (if provided), ignoring case, and not uninstall/setup-like.
if (!string.IsNullOrWhiteSpace(expectedExeName))
{
foreach (var exe in allExes)
{
if (string.Equals(Path.GetFileName(exe), expectedExeName, StringComparison.OrdinalIgnoreCase) &&
!LooksLikeUninstallerOrSetup(exe))
{
result.Add(exe);
}
}
}
// 2) All other non-uninstall/setup exes
foreach (var exe in allExes)
{
if (LooksLikeUninstallerOrSetup(exe))
{
continue;
}
// Skip ones already added as expectedExeName matches
if (result.Contains(exe, StringComparer.OrdinalIgnoreCase))
{
continue;
}
result.Add(exe);
}
// 3) We intentionally do NOT add uninstall/setup/update exes here.
// If you ever want them, you can add a separate API to expose them.
return result;
}
private static IEnumerable<string> GetBinExes(string root)
{
var bin = Path.Combine(root, "bin");
return !Directory.Exists(bin)
? []
: Directory.EnumerateFiles(bin, "*.exe", SearchOption.TopDirectoryOnly);
}
private static bool LooksLikeUninstallerOrSetup(string path)
{
var name = Path.GetFileName(path);
return name.StartsWith("unins", StringComparison.OrdinalIgnoreCase) // e.g. Inno: unins000.exe
|| name.Contains("setup", StringComparison.OrdinalIgnoreCase) // setup.exe
|| name.Contains("installer", StringComparison.OrdinalIgnoreCase) // installer.exe / MyAppInstaller.exe
|| name.Contains("update", StringComparison.OrdinalIgnoreCase); // updater/updater.exe
}
private static string? ExtractFirstPath(string? commandLine)
{
if (string.IsNullOrWhiteSpace(commandLine))
{
return null;
}
commandLine = commandLine.Trim();
if (commandLine.StartsWith('"'))
{
var endQuote = commandLine.IndexOf('"', 1);
if (endQuote > 1)
{
return commandLine[1..endQuote];
}
}
var firstSpace = commandLine.IndexOf(' ');
var candidate = firstSpace > 0 ? commandLine[..firstSpace] : commandLine;
candidate = candidate.Trim('"');
return candidate.Length > 0 ? candidate : null;
}
}

View File

@@ -558,6 +558,7 @@ public class UWPApplication : IUWPApplication
IsPackaged = true,
Commands = app.GetCommands(),
AppIdentifier = app.GetAppIdentifier(),
PackageFamilyName = app.Package.FamilyName,
};
return item;
}

View File

@@ -1065,6 +1065,7 @@ public class Win32Program : IProgram
DirPath = app.Location,
Commands = app.GetCommands(),
AppIdentifier = app.GetAppIdentifier(),
FullExecutablePath = app.FullPath,
};
}
}

View File

@@ -62,7 +62,7 @@ public partial class InstallPackageCommand : InvokableCommand
{
PackageInstallCommandState.Install => Icons.DownloadIcon,
PackageInstallCommandState.Update => Icons.UpdateIcon,
PackageInstallCommandState.Uninstall => Icons.CompletedIcon,
PackageInstallCommandState.Uninstall => Icons.DeleteIcon,
_ => throw new NotImplementedException(),
};
Name = InstallCommandState switch

View File

@@ -194,46 +194,95 @@ public partial class InstallPackageListItem : ListItem
var isInstalled = _package.InstalledVersion is not null;
var installedState = isInstalled ?
(_package.IsUpdateAvailable ?
PackageInstallCommandState.Update : PackageInstallCommandState.Uninstall) :
(_package.IsUpdateAvailable ? PackageInstallCommandState.Update : PackageInstallCommandState.Uninstall) :
PackageInstallCommandState.Install;
// might be an uninstall command
InstallPackageCommand installCommand = new(_package, installedState);
if (isInstalled)
if (_package.InstalledVersion is not null)
{
this.Icon = installCommand.Icon;
this.Command = new NoOpCommand();
#if DEBUG
var installerType = _package.InstalledVersion.GetMetadata(PackageVersionMetadataField.InstallerType);
Subtitle = installerType + " | " + Subtitle;
#endif
List<IContextItem> contextMenu = [];
CommandContextItem uninstallContextItem = new(installCommand)
Command = installCommand;
Icon = installedState switch
{
IsCritical = true,
Icon = Icons.DeleteIcon,
PackageInstallCommandState.Install => Icons.DownloadIcon,
PackageInstallCommandState.Update => Icons.UpdateIcon,
PackageInstallCommandState.Uninstall => Icons.CompletedIcon,
_ => Icons.DownloadIcon,
};
if (WinGetStatics.AppSearchCallback is not null)
TryLocateAndAppendActionForApp(contextMenu);
MoreCommands = contextMenu.ToArray();
}
else
{
_installCommand = new InstallPackageCommand(_package, installedState);
_installCommand.InstallStateChanged += InstallStateChangedHandler;
Command = _installCommand;
Icon = _installCommand.Icon;
}
}
private void TryLocateAndAppendActionForApp(List<IContextItem> contextMenu)
{
try
{
// Let's try to connect it to an installed app if possible
// This is a bit of dark magic, since there's no direct link between
// WinGet packages and installed apps.
var lookupByPackageName = WinGetStatics.AppSearchByPackageFamilyNameCallback;
if (lookupByPackageName is not null)
{
var callback = WinGetStatics.AppSearchCallback;
var installedApp = callback(_package.DefaultInstallVersion is null ? _package.Name : _package.DefaultInstallVersion.DisplayName);
if (installedApp is not null)
var names = _package.InstalledVersion.PackageFamilyNames;
for (var i = 0; i < names.Count; i++)
{
this.Command = installedApp.Command;
contextMenu = [.. installedApp.MoreCommands];
var installedAppByPfn = lookupByPackageName(names[i]);
if (installedAppByPfn is not null)
{
contextMenu.Add(new Separator());
contextMenu.Add(new CommandContextItem(installedAppByPfn.Command));
foreach (var item in installedAppByPfn.MoreCommands)
{
contextMenu.Add(item);
}
return;
}
}
}
contextMenu.Add(uninstallContextItem);
this.MoreCommands = contextMenu.ToArray();
return;
var lookupByProductCode = WinGetStatics.AppSearchByProductCodeCallback;
if (lookupByProductCode is not null)
{
var productCodes = _package.InstalledVersion.ProductCodes;
for (var i = 0; i < productCodes.Count; i++)
{
var installedAppByProductCode = lookupByProductCode(productCodes[i]);
if (installedAppByProductCode is not null)
{
contextMenu.Add(new Separator());
contextMenu.Add(new CommandContextItem(installedAppByProductCode.Command));
foreach (var item in installedAppByProductCode.MoreCommands)
{
contextMenu.Add(item);
}
return;
}
}
}
}
catch (Exception ex)
{
Logger.LogError($"Failed to retrieve app context menu items for package '{_package?.Name ?? "Unknown"}'", ex);
}
// didn't find the app
_installCommand = new InstallPackageCommand(_package, installedState);
this.Command = _installCommand;
Icon = _installCommand.Icon;
_installCommand.InstallStateChanged += InstallStateChangedHandler;
}
private void InstallStateChangedHandler(object? sender, InstallPackageCommand e)

View File

@@ -41,5 +41,9 @@ public partial class WinGetExtensionCommandsProvider : CommandProvider
public override void InitializeWithHost(IExtensionHost host) => WinGetExtensionHost.Instance.Initialize(host);
public void SetAllLookup(Func<string, ICommandItem?> callback) => WinGetStatics.AppSearchCallback = callback;
public void SetAllLookup(Func<string, ICommandItem?> lookupByPackageName, Func<string, ICommandItem?> lookupByProductCode)
{
WinGetStatics.AppSearchByPackageFamilyNameCallback = lookupByPackageName;
WinGetStatics.AppSearchByProductCodeCallback = lookupByProductCode;
}
}

View File

@@ -34,7 +34,9 @@ internal static class WinGetStatics
private static readonly StatusMessage _errorMessage = new() { State = MessageState.Error };
public static Func<string, ICommandItem?>? AppSearchCallback { get; set; }
public static Func<string, ICommandItem?>? AppSearchByPackageFamilyNameCallback { get; set; }
public static Func<string, ICommandItem?>? AppSearchByProductCodeCallback { get; set; }
private static readonly CompositeFormat CreateCatalogErrorMessage = System.Text.CompositeFormat.Parse(Properties.Resources.winget_create_catalog_error);

View File

@@ -56,7 +56,7 @@ if ($IsAzurePipelineBuild) {
}
if (($BuildStep -ieq "all") -Or ($BuildStep -ieq "build")) {
& $nugetPath restore (Join-Path $PSScriptRoot "..\..\..\..\..\PowerToys.sln")
& $nugetPath restore (Join-Path $PSScriptRoot "..\..\..\..\..\PowerToys.slnx")
Try {
foreach ($config in $Configuration.Split(",")) {

View File

@@ -224,7 +224,7 @@ namespace PowerAccent.Core
LetterKey.VK_X => new[] { "ẋ", "×" },
LetterKey.VK_Y => new[] { "ẏ", "ꝡ" },
LetterKey.VK_Z => new[] { "ʒ", "ǯ", "" },
LetterKey.VK_COMMA => new[] { "∙", "₋", "⁻", "", "√" }, // is in VK_MINUS for other languages, but not VK_COMMA, so we add it here.
LetterKey.VK_COMMA => new[] { "∙", "₋", "⁻", "", "√", "‟", "《", "》", "", "〈", "〉", "″", "‴", "⁗" }, // is in VK_MINUS for other languages, but not VK_COMMA, so we add it here.
LetterKey.VK_PERIOD => new[] { "…", "⁝", "\u0300", "\u0301", "\u0302", "\u0303", "\u0304", "\u0308", "\u030B", "\u030C" },
LetterKey.VK_MINUS => new[] { "~", "", "", "", "—", "―", "", "", "⸺", "⸻", "∓" },
LetterKey.VK_SLASH_ => new[] { "÷", "√" },
@@ -302,6 +302,7 @@ namespace PowerAccent.Core
LetterKey.VK_E => new[] { "€" },
LetterKey.VK_S => new[] { "š" },
LetterKey.VK_Z => new[] { "ž" },
LetterKey.VK_COMMA => new[] { "„", "“", "»", "«" },
_ => Array.Empty<string>(),
};
}
@@ -317,6 +318,7 @@ namespace PowerAccent.Core
LetterKey.VK_U => new[] { "ü" },
LetterKey.VK_Z => new[] { "ž" },
LetterKey.VK_S => new[] { "š" },
LetterKey.VK_COMMA => new[] { "„", "“", "«", "»" },
_ => Array.Empty<string>(),
};
}
@@ -344,6 +346,7 @@ namespace PowerAccent.Core
LetterKey.VK_A => new[] { "ä", "å" },
LetterKey.VK_E => new[] { "€" },
LetterKey.VK_O => new[] { "ö" },
LetterKey.VK_COMMA => new[] { "”", "", "»" },
_ => Array.Empty<string>(),
};
}
@@ -360,6 +363,7 @@ namespace PowerAccent.Core
LetterKey.VK_O => new[] { "ô", "ö", "ó", "ò", "õ", "œ" },
LetterKey.VK_U => new[] { "û", "ù", "ü", "ú" },
LetterKey.VK_Y => new[] { "ÿ", "ý" },
LetterKey.VK_COMMA => new[] { "«", "»", "", "", "“", "”", "", "" },
_ => Array.Empty<string>(),
};
}
@@ -376,6 +380,7 @@ namespace PowerAccent.Core
LetterKey.VK_U => new[] { "ú" },
LetterKey.VK_Y => new[] { "ý" },
LetterKey.VK_T => new[] { "þ" },
LetterKey.VK_COMMA => new[] { "„", "“", "", "" },
_ => Array.Empty<string>(),
};
}
@@ -393,7 +398,7 @@ namespace PowerAccent.Core
LetterKey.VK_N => new[] { "ñ" },
LetterKey.VK_O => new[] { "ó" },
LetterKey.VK_U => new[] { "ú", "ü" },
LetterKey.VK_COMMA => new[] { "¿", "?", "¡", "!" },
LetterKey.VK_COMMA => new[] { "¿", "?", "¡", "!", "«", "»", "“", "”", "", "" },
_ => Array.Empty<string>(),
};
}
@@ -411,7 +416,7 @@ namespace PowerAccent.Core
LetterKey.VK_O => new[] { "ò", "ó" },
LetterKey.VK_U => new[] { "ù", "ú", "ü" },
LetterKey.VK_L => new[] { "·" },
LetterKey.VK_COMMA => new[] { "¿", "?", "¡", "!" },
LetterKey.VK_COMMA => new[] { "¿", "?", "¡", "!", "«", "»", "“", "”", "", "" },
_ => Array.Empty<string>(),
};
}
@@ -427,6 +432,7 @@ namespace PowerAccent.Core
LetterKey.VK_O => new[] { "ō" },
LetterKey.VK_S => new[] { "$" },
LetterKey.VK_U => new[] { "ū" },
LetterKey.VK_COMMA => new[] { "“", "”", "", "" },
_ => Array.Empty<string>(),
};
}
@@ -443,6 +449,7 @@ namespace PowerAccent.Core
LetterKey.VK_N => new[] { "ñ" },
LetterKey.VK_O => new[] { "ó", "ö", "ô" },
LetterKey.VK_U => new[] { "ú", "ü", "û" },
LetterKey.VK_COMMA => new[] { "“", "„", "”", "", ",", "" },
_ => Array.Empty<string>(),
};
}
@@ -469,6 +476,7 @@ namespace PowerAccent.Core
LetterKey.VK_V => new[] { "ü", "ǖ", "ǘ", "ǚ", "ǜ" },
LetterKey.VK_Y => new[] { "¥" },
LetterKey.VK_Z => new[] { "ẑ" },
LetterKey.VK_COMMA => new[] { "“", "”", "", "", "「", "」", "『", "』" },
_ => Array.Empty<string>(),
};
}
@@ -505,6 +513,7 @@ namespace PowerAccent.Core
LetterKey.VK_S => new[] { "ş" },
LetterKey.VK_T => new[] { "₺" },
LetterKey.VK_U => new[] { "ü", "û" },
LetterKey.VK_COMMA => new[] { "“", "”", "", "", "«", "»", "", "" },
_ => Array.Empty<string>(),
};
}
@@ -522,6 +531,7 @@ namespace PowerAccent.Core
LetterKey.VK_O => new[] { "ó" },
LetterKey.VK_S => new[] { "ś" },
LetterKey.VK_Z => new[] { "ż", "ź" },
LetterKey.VK_COMMA => new[] { "„", "”", "", "", "»", "«" },
_ => Array.Empty<string>(),
};
}
@@ -539,7 +549,7 @@ namespace PowerAccent.Core
LetterKey.VK_P => new[] { "π" },
LetterKey.VK_S => new[] { "$" },
LetterKey.VK_U => new[] { "ú" },
LetterKey.VK_COMMA => new[] { "≤", "≥", "≠", "≈", "≙", "±", "₊", "⁺" },
LetterKey.VK_COMMA => new[] { "≤", "≥", "≠", "≈", "≙", "±", "₊", "⁺", "“", "”", "", "", "«", "»" },
_ => Array.Empty<string>(),
};
}
@@ -594,6 +604,7 @@ namespace PowerAccent.Core
LetterKey.VK_U => new[] { "ú" },
LetterKey.VK_Y => new[] { "ý" },
LetterKey.VK_Z => new[] { "ž" },
LetterKey.VK_COMMA => new[] { "„", "“", "", "", "»", "«", "", "" },
_ => Array.Empty<string>(),
};
}
@@ -608,6 +619,7 @@ namespace PowerAccent.Core
LetterKey.VK_I => new[] { "í" },
LetterKey.VK_O => new[] { "ó" },
LetterKey.VK_U => new[] { "ú" },
LetterKey.VK_COMMA => new[] { "“", "”", "", "" },
_ => Array.Empty<string>(),
};
}
@@ -623,6 +635,7 @@ namespace PowerAccent.Core
LetterKey.VK_O => new[] { "ò" },
LetterKey.VK_P => new[] { "£" },
LetterKey.VK_U => new[] { "ù" },
LetterKey.VK_COMMA => new[] { "“", "”", "", "" },
_ => Array.Empty<string>(),
};
}
@@ -645,6 +658,7 @@ namespace PowerAccent.Core
LetterKey.VK_U => new[] { "ů", "ú" },
LetterKey.VK_Y => new[] { "ý" },
LetterKey.VK_Z => new[] { "ž" },
LetterKey.VK_COMMA => new[] { "„", "“", "", "", "»", "«", "", "" },
_ => Array.Empty<string>(),
};
}
@@ -659,6 +673,7 @@ namespace PowerAccent.Core
LetterKey.VK_O => new[] { "ö" },
LetterKey.VK_S => new[] { "ß" },
LetterKey.VK_U => new[] { "ü" },
LetterKey.VK_COMMA => new[] { "„", "“", "", "", "»", "«", "", "" },
_ => Array.Empty<string>(),
};
}
@@ -689,6 +704,7 @@ namespace PowerAccent.Core
LetterKey.VK_X => new string[] { "ξ" },
LetterKey.VK_Y => new string[] { "υ" },
LetterKey.VK_Z => new string[] { "ζ" },
LetterKey.VK_COMMA => new[] { "“", "”", "«", "»", },
_ => Array.Empty<string>(),
};
}
@@ -710,7 +726,7 @@ namespace PowerAccent.Core
LetterKey.VK_U => new[] { "וֹ", "וּ", "װ", "\u05b9" },
LetterKey.VK_X => new[] { "\u05b6", "\u05b1" },
LetterKey.VK_Y => new[] { "ױ" },
LetterKey.VK_COMMA => new[] { "”", "", "״", "׳" },
LetterKey.VK_COMMA => new[] { "”", "", "'", "״", "׳" },
LetterKey.VK_PERIOD => new[] { "\u05ab", "\u05bd", "\u05bf" },
LetterKey.VK_MINUS => new[] { "", "־" },
_ => Array.Empty<string>(),
@@ -727,6 +743,7 @@ namespace PowerAccent.Core
LetterKey.VK_I => new[] { "í" },
LetterKey.VK_O => new[] { "ó", "ő", "ö" },
LetterKey.VK_U => new[] { "ú", "ű", "ü" },
LetterKey.VK_COMMA => new[] { "„", "”", "»", "«" },
_ => Array.Empty<string>(),
};
}
@@ -740,6 +757,7 @@ namespace PowerAccent.Core
LetterKey.VK_I => new[] { "î" },
LetterKey.VK_S => new[] { "ș" },
LetterKey.VK_T => new[] { "ț" },
LetterKey.VK_COMMA => new[] { "„", "”", "«", "»" },
_ => Array.Empty<string>(),
};
}
@@ -754,6 +772,7 @@ namespace PowerAccent.Core
LetterKey.VK_I => new[] { "ì", "í" },
LetterKey.VK_O => new[] { "ò", "ó" },
LetterKey.VK_U => new[] { "ù", "ú" },
LetterKey.VK_COMMA => new[] { "«", "»", "“", "”", "", "" },
_ => Array.Empty<string>(),
};
}
@@ -772,6 +791,7 @@ namespace PowerAccent.Core
LetterKey.VK_R => new[] { "ř" },
LetterKey.VK_S => new[] { "ş" },
LetterKey.VK_U => new[] { "û", "ü" },
LetterKey.VK_COMMA => new[] { "«", "»", "“", "”" },
_ => Array.Empty<string>(),
};
}
@@ -789,6 +809,7 @@ namespace PowerAccent.Core
LetterKey.VK_U => new[] { "û", "ü", "ù", "ú" },
LetterKey.VK_Y => new[] { "ŷ", "ÿ", "ỳ", "ý" },
LetterKey.VK_W => new[] { "ŵ", "ẅ", "ẁ", "ẃ" },
LetterKey.VK_COMMA => new[] { "", "", "“", "“" },
_ => Array.Empty<string>(),
};
}
@@ -801,6 +822,7 @@ namespace PowerAccent.Core
LetterKey.VK_A => new[] { "å", "ä" },
LetterKey.VK_E => new[] { "é" },
LetterKey.VK_O => new[] { "ö" },
LetterKey.VK_COMMA => new[] { "”", "", "»", "«" },
_ => Array.Empty<string>(),
};
}
@@ -814,6 +836,7 @@ namespace PowerAccent.Core
LetterKey.VK_D => new[] { "đ" },
LetterKey.VK_S => new[] { "š" },
LetterKey.VK_Z => new[] { "ž" },
LetterKey.VK_COMMA => new[] { "„", "“", "", "", "»", "«", "", "" },
_ => Array.Empty<string>(),
};
}
@@ -838,6 +861,7 @@ namespace PowerAccent.Core
{
LetterKey.VK_E => new[] { "ѐ" },
LetterKey.VK_I => new[] { "ѝ" },
LetterKey.VK_COMMA => new[] { "„", "“", "", "" },
_ => Array.Empty<string>(),
};
}
@@ -869,6 +893,7 @@ namespace PowerAccent.Core
LetterKey.VK_E => new[] { "€", "é" },
LetterKey.VK_O => new[] { "ø" },
LetterKey.VK_S => new[] { "$" },
LetterKey.VK_COMMA => new[] { "«", "»", ",", "", "", "„", "“" },
_ => Array.Empty<string>(),
};
}
@@ -881,6 +906,7 @@ namespace PowerAccent.Core
LetterKey.VK_A => new[] { "å", "æ" },
LetterKey.VK_E => new[] { "€" },
LetterKey.VK_O => new[] { "ø" },
LetterKey.VK_COMMA => new[] { "»", "«", "“", "”", "", "", "", "" },
_ => Array.Empty<string>(),
};
}
@@ -897,6 +923,7 @@ namespace PowerAccent.Core
LetterKey.VK_S => new[] { "š" },
LetterKey.VK_U => new[] { "ų", "ū" },
LetterKey.VK_Z => new[] { "ž" },
LetterKey.VK_COMMA => new[] { "„", "“", "", "" },
_ => Array.Empty<string>(),
};
}
@@ -910,6 +937,7 @@ namespace PowerAccent.Core
LetterKey.VK_E => new[] { "€" },
LetterKey.VK_S => new[] { "š" },
LetterKey.VK_Z => new[] { "ž" },
LetterKey.VK_COMMA => new[] { "„", "“", "»", "«" },
_ => Array.Empty<string>(),
};
}

View File

@@ -20,6 +20,7 @@ public partial class PowerAccent : IDisposable
// Keys that show a description (like dashes) when ShowCharacterInfoSetting is 1
private readonly LetterKey[] _letterKeysShowingDescription = new LetterKey[] { LetterKey.VK_O };
private const double ScreenMinPadding = 150;
private bool _visible;
private string[] _characters = Array.Empty<string>();
@@ -332,6 +333,11 @@ public partial class PowerAccent : IDisposable
return Calculation.GetRawCoordinatesFromPosition(position, screen, window);
}
public double GetDisplayMaxWidth()
{
return WindowsFunctions.GetActiveDisplay().Size.Width - ScreenMinPadding;
}
public Position GetToolbarPosition()
{
return _settingService.Position;

View File

@@ -59,6 +59,7 @@ public partial class Selector : FluentWindow, IDisposable, INotifyPropertyChange
_selectedIndex = index;
characters.SelectedIndex = _selectedIndex;
characterName.Text = _powerAccent.CharacterDescriptions[_selectedIndex];
characters.ScrollIntoView(character);
}
private void PowerAccent_OnChangeDisplay(bool isActive, string[] chars)
@@ -73,6 +74,7 @@ public partial class Selector : FluentWindow, IDisposable, INotifyPropertyChange
characters.ItemsSource = chars;
characters.SelectedIndex = _selectedIndex;
this.UpdateLayout(); // Required for filling the actual width/height before positioning.
SetWindowsSize();
SetWindowPosition();
Show();
Microsoft.PowerToys.Telemetry.PowerToysTelemetry.Log.WriteEvent(new PowerAccent.Core.Telemetry.PowerAccentShowAccentMenuEvent());
@@ -96,6 +98,11 @@ public partial class Selector : FluentWindow, IDisposable, INotifyPropertyChange
this.Top = position.Y;
}
private void SetWindowsSize()
{
this.characters.MaxWidth = _powerAccent.GetDisplayMaxWidth();
}
protected override void OnClosed(EventArgs e)
{
_powerAccent.SaveUsageInfo();

Binary file not shown.

View File

@@ -10,7 +10,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library;
public sealed class AdvancedPasteCustomActions
{
private static readonly JsonSerializerOptions _serializerOptions = new()
private static readonly JsonSerializerOptions _serializerOptions = new(SettingsSerializationContext.Default.Options)
{
WriteIndented = true,
};

View File

@@ -104,6 +104,6 @@ namespace Microsoft.PowerToys.Settings.UI.Library
public PasteAIConfiguration PasteAIConfiguration { get; set; }
public override string ToString()
=> JsonSerializer.Serialize(this);
=> JsonSerializer.Serialize(this, SettingsSerializationContext.Default.AdvancedPasteProperties);
}
}

View File

@@ -2,13 +2,32 @@
// 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.Text.Json;
using System.Text.Json.Serialization;
namespace Microsoft.PowerToys.Settings.UI.Library
{
/// <summary>
/// Base class for all PowerToys module settings.
/// </summary>
/// <remarks>
/// <para><strong>IMPORTANT for Native AOT compatibility:</strong></para>
/// <para>When creating a new class that inherits from <see cref="BasePTModuleSettings"/>,
/// you MUST register it in <see cref="SettingsSerializationContext"/> by adding a
/// <c>[JsonSerializable(typeof(YourNewSettingsClass))]</c> attribute.</para>
/// <para>Failure to register the type will cause <see cref="ToJsonString"/> to throw
/// <see cref="InvalidOperationException"/> at runtime.</para>
/// <para>See <see cref="SettingsSerializationContext"/> for registration instructions.</para>
/// </remarks>
public abstract class BasePTModuleSettings
{
// Cached JsonSerializerOptions for Native AOT compatibility
private static readonly JsonSerializerOptions _jsonSerializerOptions = new JsonSerializerOptions
{
TypeInfoResolver = SettingsSerializationContext.Default,
};
// Gets or sets name of the powertoy module.
[JsonPropertyName("name")]
public string Name { get; set; }
@@ -17,11 +36,33 @@ namespace Microsoft.PowerToys.Settings.UI.Library
[JsonPropertyName("version")]
public string Version { get; set; }
// converts the current to a json string.
/// <summary>
/// Converts the current settings object to a JSON string.
/// </summary>
/// <returns>JSON string representation of this settings object.</returns>
/// <exception cref="InvalidOperationException">
/// Thrown when the runtime type is not registered in <see cref="SettingsSerializationContext"/>.
/// All derived types must be registered with <c>[JsonSerializable(typeof(YourType))]</c> attribute.
/// </exception>
/// <remarks>
/// This method uses Native AOT-compatible JSON serialization. The runtime type must be
/// registered in <see cref="SettingsSerializationContext"/> for serialization to work.
/// </remarks>
public virtual string ToJsonString()
{
// By default JsonSerializer will only serialize the properties in the base class. This can be avoided by passing the object type (more details at https://stackoverflow.com/a/62498888)
return JsonSerializer.Serialize(this, GetType());
var runtimeType = GetType();
// For Native AOT compatibility, get JsonTypeInfo from the TypeInfoResolver
var typeInfo = _jsonSerializerOptions.TypeInfoResolver?.GetTypeInfo(runtimeType, _jsonSerializerOptions);
if (typeInfo == null)
{
throw new InvalidOperationException($"Type {runtimeType.FullName} is not registered in SettingsSerializationContext. Please add it to the [JsonSerializable] attributes.");
}
// Use AOT-friendly serialization
return JsonSerializer.Serialize(this, typeInfo);
}
public override int GetHashCode()

View File

@@ -37,7 +37,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
public override string ToString()
{
return JsonSerializer.Serialize(this);
return JsonSerializer.Serialize(this, SettingsSerializationContext.Default.BoolProperty);
}
public bool TryToCmdRepresentable(out string result)

View File

@@ -12,14 +12,14 @@ namespace Microsoft.PowerToys.Settings.UI.Library
{
public override bool Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var boolProperty = JsonSerializer.Deserialize<BoolProperty>(ref reader, options);
var boolProperty = JsonSerializer.Deserialize(ref reader, SettingsSerializationContext.Default.BoolProperty);
return boolProperty.Value;
}
public override void Write(Utf8JsonWriter writer, bool value, JsonSerializerOptions options)
{
var boolProperty = new BoolProperty(value);
JsonSerializer.Serialize(writer, boolProperty, options);
JsonSerializer.Serialize(writer, boolProperty, SettingsSerializationContext.Default.BoolProperty);
}
}
}

View File

@@ -43,7 +43,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
if (doc.RootElement.TryGetProperty(nameof(Hotkey), out JsonElement hotkeyElement))
{
Hotkey = JsonSerializer.Deserialize<HotkeySettings>(hotkeyElement.GetRawText());
Hotkey = JsonSerializer.Deserialize(hotkeyElement.GetRawText(), SettingsSerializationContext.Default.HotkeySettings);
}
}
catch (Exception)

View File

@@ -87,6 +87,6 @@ namespace Microsoft.PowerToys.Settings.UI.Library
public bool ShowColorName { get; set; }
public override string ToString()
=> JsonSerializer.Serialize(this);
=> JsonSerializer.Serialize(this, SettingsSerializationContext.Default.ColorPickerProperties);
}
}

View File

@@ -54,6 +54,6 @@ namespace Microsoft.PowerToys.Settings.UI.Library
public bool ShowColorName { get; set; }
public override string ToString()
=> JsonSerializer.Serialize(this);
=> JsonSerializer.Serialize(this, SettingsSerializationContext.Default.ColorPickerPropertiesVersion1);
}
}

View File

@@ -27,7 +27,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
// Returns a JSON version of the class settings configuration class.
public override string ToString()
{
return JsonSerializer.Serialize(this);
return JsonSerializer.Serialize(this, SettingsSerializationContext.Default.DoubleProperty);
}
}
}

View File

@@ -21,7 +21,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
public string ToJsonString()
{
return JsonSerializer.Serialize(this);
return JsonSerializer.Serialize(this, SettingsSerializationContext.Default.FileLocksmithLocalProperties);
}
// This function is required to implement the ISettingsConfig interface and obtain the settings configurations.

View File

@@ -17,6 +17,6 @@ namespace Microsoft.PowerToys.Settings.UI.Library
[JsonPropertyName("bool_show_extended_menu")]
public BoolProperty ExtendedContextMenuOnly { get; set; }
public override string ToString() => JsonSerializer.Serialize(this);
public override string ToString() => JsonSerializer.Serialize(this, SettingsSerializationContext.Default.FileLocksmithProperties);
}
}

View File

@@ -119,7 +119,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
// converts the current to a json string.
public string ToJsonString()
{
return JsonSerializer.Serialize(this);
return JsonSerializer.Serialize(this, SettingsSerializationContext.Default.GeneralSettings);
}
private static string DefaultPowertoysVersion()

View File

@@ -23,7 +23,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
public override string ToString()
{
return JsonSerializer.Serialize(this);
return JsonSerializer.Serialize(this, SettingsSerializationContext.Default.GeneralSettingsCustomAction);
}
}
}

View File

@@ -12,13 +12,18 @@ namespace Microsoft.PowerToys.Settings.UI.Library.Helpers
{
public struct SunTimes
{
public int SunriseHour;
public int SunriseMinute;
public int SunsetHour;
public int SunsetMinute;
public string Text;
public int SunriseHour { get; set; }
public bool HasSunrise;
public bool HasSunset;
public int SunriseMinute { get; set; }
public int SunsetHour { get; set; }
public int SunsetMinute { get; set; }
public string Text { get; set; }
public bool HasSunrise { get; set; }
public bool HasSunset { get; set; }
}
}

View File

@@ -86,7 +86,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
public string ToJsonString()
{
return JsonSerializer.Serialize(this);
return JsonSerializer.Serialize(this, SettingsSerializationContext.Default.ImageResizerProperties);
}
}

View File

@@ -37,8 +37,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
public override string ToJsonString()
{
var options = _serializerOptions;
return JsonSerializer.Serialize(this, options);
return JsonSerializer.Serialize(this, SettingsSerializationContext.Default.ImageResizerSettings);
}
public string GetModuleName()

View File

@@ -42,7 +42,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
// Returns a JSON version of the class settings configuration class.
public override string ToString()
{
return JsonSerializer.Serialize(this);
return JsonSerializer.Serialize(this, SettingsSerializationContext.Default.IntProperty);
}
public static implicit operator IntProperty(int v)

View File

@@ -34,7 +34,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
public string ToJsonString()
{
return JsonSerializer.Serialize(this);
return JsonSerializer.Serialize(this, SettingsSerializationContext.Default.KeyboardManagerProfile);
}
public string GetModuleName()

View File

@@ -46,6 +46,6 @@ namespace Microsoft.PowerToys.Settings.UI.Library
public IntProperty DefaultMeasureStyle { get; set; }
public override string ToString() => JsonSerializer.Serialize(this);
public override string ToString() => JsonSerializer.Serialize(this, SettingsSerializationContext.Default.MeasureToolProperties);
}
}

View File

@@ -15,8 +15,9 @@ namespace Microsoft.PowerToys.Settings.UI.Library
public struct ConnectionRequest
#pragma warning restore SA1649 // File name should match first type name
{
public string PCName;
public string SecurityKey;
public string PCName { get; set; }
public string SecurityKey { get; set; }
}
public struct NewKeyGenerationRequest

View File

@@ -33,6 +33,6 @@ namespace Microsoft.PowerToys.Settings.UI.Library
[JsonPropertyName("ReplaceVariables")]
public BoolProperty ReplaceVariables { get; set; }
public override string ToString() => JsonSerializer.Serialize(this);
public override string ToString() => JsonSerializer.Serialize(this, SettingsSerializationContext.Default.NewPlusProperties);
}
}

View File

@@ -23,7 +23,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
public override string ToString()
{
return JsonSerializer.Serialize(this);
return JsonSerializer.Serialize(this, SettingsSerializationContext.Default.OutGoingGeneralSettings);
}
}
}

View File

@@ -23,7 +23,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
public override string ToString()
{
return JsonSerializer.Serialize(this);
return JsonSerializer.Serialize(this, SettingsSerializationContext.Default.OutGoingLanguageSettings);
}
}
}

View File

@@ -64,7 +64,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
public AIServiceType ActiveServiceTypeKind => ActiveProvider?.ServiceTypeKind ?? AIServiceType.OpenAI;
public override string ToString()
=> JsonSerializer.Serialize(this);
=> JsonSerializer.Serialize(this, SettingsSerializationContext.Default.PasteAIConfiguration);
protected bool SetProperty<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
{

View File

@@ -34,7 +34,7 @@ namespace Settings.UI.Library
public string ToJsonString()
{
return JsonSerializer.Serialize(this);
return JsonSerializer.Serialize(this, Microsoft.PowerToys.Settings.UI.Library.SettingsSerializationContext.Default.PeekPreviewSettings);
}
public string GetModuleName()

View File

@@ -32,6 +32,6 @@ namespace Microsoft.PowerToys.Settings.UI.Library
public BoolProperty EnableSpaceToActivate { get; set; }
public override string ToString() => JsonSerializer.Serialize(this);
public override string ToString() => JsonSerializer.Serialize(this, SettingsSerializationContext.Default.PeekProperties);
}
}

View File

@@ -24,6 +24,6 @@ namespace Microsoft.PowerToys.Settings.UI.Library
public string PreferredLanguage { get; set; }
public override string ToString()
=> JsonSerializer.Serialize(this);
=> JsonSerializer.Serialize(this, SettingsSerializationContext.Default.PowerOcrProperties);
}
}

View File

@@ -340,7 +340,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
public override string ToString()
{
return JsonSerializer.Serialize(this);
return JsonSerializer.Serialize(this, SettingsSerializationContext.Default.PowerPreviewProperties);
}
private static void LogTelemetryEvent(bool value, [CallerMemberName] string propertyName = null)

View File

@@ -54,7 +54,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
public string ToJsonString()
{
return JsonSerializer.Serialize(this);
return JsonSerializer.Serialize(this, SettingsSerializationContext.Default.PowerRenameLocalProperties);
}
// This function is required to implement the ISettingsConfig interface and obtain the settings configurations.

View File

@@ -0,0 +1,172 @@
// 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.Text.Json.Serialization;
using SettingsUILibrary = Settings.UI.Library;
using SettingsUILibraryHelpers = Settings.UI.Library.Helpers;
namespace Microsoft.PowerToys.Settings.UI.Library
{
/// <summary>
/// JSON serialization context for Native AOT compatibility.
/// This context provides source-generated serialization for all PowerToys settings types.
/// </summary>
/// <remarks>
/// <para><strong>⚠️ CRITICAL REQUIREMENT FOR ALL NEW SETTINGS CLASSES ⚠️</strong></para>
/// <para>
/// When adding a new PowerToys module or any class that inherits from <see cref="BasePTModuleSettings"/>,
/// you <strong>MUST</strong> add a <c>[JsonSerializable(typeof(YourNewSettingsClass))]</c> attribute
/// to this class. This is a MANDATORY step for Native AOT compatibility.
/// </para>
/// <para><strong>Steps to add a new settings class:</strong></para>
/// <list type="number">
/// <item><description>Create your new settings class (e.g., <c>MyNewModuleSettings</c>) that inherits from <see cref="BasePTModuleSettings"/></description></item>
/// <item><description>Add <c>[JsonSerializable(typeof(MyNewModuleSettings))]</c> attribute to this <see cref="SettingsSerializationContext"/> class</description></item>
/// <item><description>If you have a corresponding Properties class, also add <c>[JsonSerializable(typeof(MyNewModuleProperties))]</c></description></item>
/// <item><description>Rebuild the project - source generator will create serialization code at compile time</description></item>
/// </list>
/// <para><strong>⚠️ Failure to register types will cause runtime errors:</strong></para>
/// <para>
/// If you forget to add the <c>[JsonSerializable]</c> attribute, calling <c>ToJsonString()</c> or
/// deserialization methods will throw <see cref="InvalidOperationException"/> at runtime with a clear
/// error message indicating which type is missing registration.
/// </para>
/// </remarks>
[JsonSourceGenerationOptions(
WriteIndented = false,
DefaultIgnoreCondition = JsonIgnoreCondition.Never,
IncludeFields = true)]
// Main Settings Classes
[JsonSerializable(typeof(GeneralSettings))]
[JsonSerializable(typeof(AdvancedPasteSettings))]
[JsonSerializable(typeof(AlwaysOnTopSettings))]
[JsonSerializable(typeof(AwakeSettings))]
[JsonSerializable(typeof(CmdNotFoundSettings))]
[JsonSerializable(typeof(ColorPickerSettings))]
[JsonSerializable(typeof(ColorPickerSettingsVersion1))]
[JsonSerializable(typeof(CropAndLockSettings))]
[JsonSerializable(typeof(CursorWrapSettings))]
[JsonSerializable(typeof(EnvironmentVariablesSettings))]
[JsonSerializable(typeof(FancyZonesSettings))]
[JsonSerializable(typeof(FileLocksmithSettings))]
[JsonSerializable(typeof(FindMyMouseSettings))]
[JsonSerializable(typeof(HostsSettings))]
[JsonSerializable(typeof(ImageResizerSettings))]
[JsonSerializable(typeof(KeyboardManagerSettings))]
[JsonSerializable(typeof(SettingsUILibrary.LightSwitchSettings))]
[JsonSerializable(typeof(MeasureToolSettings))]
[JsonSerializable(typeof(MouseHighlighterSettings))]
[JsonSerializable(typeof(MouseJumpSettings))]
[JsonSerializable(typeof(MousePointerCrosshairsSettings))]
[JsonSerializable(typeof(MouseWithoutBordersSettings))]
[JsonSerializable(typeof(NewPlusSettings))]
[JsonSerializable(typeof(PeekSettings))]
[JsonSerializable(typeof(PowerAccentSettings))]
[JsonSerializable(typeof(PowerLauncherSettings))]
[JsonSerializable(typeof(PowerOcrSettings))]
[JsonSerializable(typeof(PowerPreviewSettings))]
[JsonSerializable(typeof(PowerRenameSettings))]
[JsonSerializable(typeof(RegistryPreviewSettings))]
[JsonSerializable(typeof(ShortcutGuideSettings))]
[JsonSerializable(typeof(WorkspacesSettings))]
[JsonSerializable(typeof(ZoomItSettings))]
// Properties Classes
[JsonSerializable(typeof(AdvancedPasteProperties))]
[JsonSerializable(typeof(AlwaysOnTopProperties))]
[JsonSerializable(typeof(AwakeProperties))]
[JsonSerializable(typeof(CmdPalProperties))]
[JsonSerializable(typeof(ColorPickerProperties))]
[JsonSerializable(typeof(ColorPickerPropertiesVersion1))]
[JsonSerializable(typeof(CropAndLockProperties))]
[JsonSerializable(typeof(CursorWrapProperties))]
[JsonSerializable(typeof(EnvironmentVariablesProperties))]
[JsonSerializable(typeof(FileLocksmithProperties))]
[JsonSerializable(typeof(FileLocksmithLocalProperties))]
[JsonSerializable(typeof(FindMyMouseProperties))]
[JsonSerializable(typeof(FZConfigProperties))]
[JsonSerializable(typeof(HostsProperties))]
[JsonSerializable(typeof(ImageResizerProperties))]
[JsonSerializable(typeof(KeyboardManagerProperties))]
[JsonSerializable(typeof(KeyboardManagerProfile))]
[JsonSerializable(typeof(LightSwitchProperties))]
[JsonSerializable(typeof(MeasureToolProperties))]
[JsonSerializable(typeof(MouseHighlighterProperties))]
[JsonSerializable(typeof(MouseJumpProperties))]
[JsonSerializable(typeof(MousePointerCrosshairsProperties))]
[JsonSerializable(typeof(MouseWithoutBordersProperties))]
[JsonSerializable(typeof(NewPlusProperties))]
[JsonSerializable(typeof(PeekProperties))]
[JsonSerializable(typeof(SettingsUILibrary.PeekPreviewSettings))]
[JsonSerializable(typeof(PowerAccentProperties))]
[JsonSerializable(typeof(PowerLauncherProperties))]
[JsonSerializable(typeof(PowerOcrProperties))]
[JsonSerializable(typeof(PowerPreviewProperties))]
[JsonSerializable(typeof(PowerRenameProperties))]
[JsonSerializable(typeof(PowerRenameLocalProperties))]
[JsonSerializable(typeof(RegistryPreviewProperties))]
[JsonSerializable(typeof(ShortcutConflictProperties))]
[JsonSerializable(typeof(ShortcutGuideProperties))]
[JsonSerializable(typeof(WorkspacesProperties))]
[JsonSerializable(typeof(ZoomItProperties))]
// Base Property Types (used throughout settings)
[JsonSerializable(typeof(BoolProperty))]
[JsonSerializable(typeof(StringProperty))]
[JsonSerializable(typeof(IntProperty))]
[JsonSerializable(typeof(DoubleProperty))]
// Helper and Utility Types
[JsonSerializable(typeof(HotkeySettings))]
[JsonSerializable(typeof(ColorFormatModel))]
[JsonSerializable(typeof(ImageSize))]
[JsonSerializable(typeof(KeysDataModel))]
[JsonSerializable(typeof(EnabledModules))]
[JsonSerializable(typeof(GeneralSettingsCustomAction))]
[JsonSerializable(typeof(OutGoingGeneralSettings))]
[JsonSerializable(typeof(OutGoingLanguageSettings))]
[JsonSerializable(typeof(AdvancedPasteCustomActions))]
[JsonSerializable(typeof(AdvancedPasteAdditionalActions))]
[JsonSerializable(typeof(AdvancedPasteCustomAction))]
[JsonSerializable(typeof(AdvancedPasteAdditionalAction))]
[JsonSerializable(typeof(AdvancedPastePasteAsFileAction))]
[JsonSerializable(typeof(AdvancedPasteTranscodeAction))]
[JsonSerializable(typeof(PasteAIConfiguration))]
[JsonSerializable(typeof(PasteAIProviderDefinition))]
[JsonSerializable(typeof(ImageResizerSizes))]
[JsonSerializable(typeof(ImageResizerCustomSizeProperty))]
[JsonSerializable(typeof(KeyboardKeysProperty))]
[JsonSerializable(typeof(SettingsUILibraryHelpers.SearchLocation))]
// IPC Send Message Wrapper Classes (Snd*)
[JsonSerializable(typeof(SndAwakeSettings))]
[JsonSerializable(typeof(SndCursorWrapSettings))]
[JsonSerializable(typeof(SndFindMyMouseSettings))]
[JsonSerializable(typeof(SndLightSwitchSettings))]
[JsonSerializable(typeof(SndMouseHighlighterSettings))]
[JsonSerializable(typeof(SndMouseJumpSettings))]
[JsonSerializable(typeof(SndMousePointerCrosshairsSettings))]
[JsonSerializable(typeof(SndPowerAccentSettings))]
[JsonSerializable(typeof(SndPowerPreviewSettings))]
[JsonSerializable(typeof(SndPowerRenameSettings))]
[JsonSerializable(typeof(SndShortcutGuideSettings))]
// IPC Message Generic Wrapper Types (SndModuleSettings<T>)
[JsonSerializable(typeof(SndModuleSettings<SndAwakeSettings>))]
[JsonSerializable(typeof(SndModuleSettings<SndCursorWrapSettings>))]
[JsonSerializable(typeof(SndModuleSettings<SndFindMyMouseSettings>))]
[JsonSerializable(typeof(SndModuleSettings<SndLightSwitchSettings>))]
[JsonSerializable(typeof(SndModuleSettings<SndMouseHighlighterSettings>))]
[JsonSerializable(typeof(SndModuleSettings<SndMouseJumpSettings>))]
[JsonSerializable(typeof(SndModuleSettings<SndMousePointerCrosshairsSettings>))]
[JsonSerializable(typeof(SndModuleSettings<SndPowerAccentSettings>))]
[JsonSerializable(typeof(SndModuleSettings<SndPowerPreviewSettings>))]
[JsonSerializable(typeof(SndModuleSettings<SndPowerRenameSettings>))]
[JsonSerializable(typeof(SndModuleSettings<SndShortcutGuideSettings>))]
public partial class SettingsSerializationContext : JsonSerializerContext
{
}
}

View File

@@ -2,6 +2,8 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#nullable enable
using System;
using System.IO;
using System.IO.Abstractions;
@@ -18,27 +20,28 @@ namespace Microsoft.PowerToys.Settings.UI.Library
private const string DefaultModuleName = "";
private readonly IFile _file;
private readonly ISettingsPath _settingsPath;
private static readonly JsonSerializerOptions _serializerOptions = new JsonSerializerOptions
{
MaxDepth = 0,
IncludeFields = true,
};
private readonly JsonSerializerOptions _serializerOptions;
public SettingsUtils()
: this(new FileSystem())
{
}
public SettingsUtils(IFileSystem fileSystem)
: this(fileSystem?.File, new SettingPath(fileSystem?.Directory, fileSystem?.Path))
public SettingsUtils(IFileSystem? fileSystem, JsonSerializerOptions? serializerOptions = null)
: this(fileSystem?.File!, new SettingPath(fileSystem?.Directory, fileSystem?.Path), serializerOptions)
{
}
public SettingsUtils(IFile file, ISettingsPath settingPath)
public SettingsUtils(IFile file, ISettingsPath settingPath, JsonSerializerOptions? serializerOptions = null)
{
_file = file ?? throw new ArgumentNullException(nameof(file));
_settingsPath = settingPath;
_serializerOptions = serializerOptions ?? new JsonSerializerOptions
{
MaxDepth = 0,
IncludeFields = true,
TypeInfoResolver = SettingsSerializationContext.Default,
};
}
public bool SettingsExists(string powertoy = DefaultModuleName, string fileName = DefaultFileName)
@@ -108,7 +111,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
/// This function creates a file in the powertoy folder if it does not exist and returns an object with default properties.
/// </summary>
/// <returns>Deserialized json settings object.</returns>
public T GetSettingsOrDefault<T, T2>(string powertoy = DefaultModuleName, string fileName = DefaultFileName, Func<object, object> settingsUpgrader = null)
public T GetSettingsOrDefault<T, T2>(string powertoy = DefaultModuleName, string fileName = DefaultFileName, Func<object, object>? settingsUpgrader = null)
where T : ISettingsConfig, new()
where T2 : ISettingsConfig, new()
{
@@ -128,7 +131,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
try
{
T2 oldSettings = GetSettings<T2>(powertoy, fileName);
T newSettings = (T)settingsUpgrader(oldSettings);
T newSettings = (T)settingsUpgrader!(oldSettings);
Logger.LogInfo($"Settings file {fileName} for {powertoy} was read successfully in the old format.");
// If the file needs to be modified, to save the new configurations accordingly.
@@ -156,7 +159,22 @@ namespace Microsoft.PowerToys.Settings.UI.Library
return newSettingsItem;
}
// Given the powerToy folder name and filename to be accessed, this function deserializes and returns the file.
/// <summary>
/// Deserializes settings from a JSON file.
/// </summary>
/// <typeparam name="T">The settings type to deserialize. Must be registered in <see cref="SettingsSerializationContext"/>.</typeparam>
/// <param name="powertoyFolderName">The PowerToy module folder name.</param>
/// <param name="fileName">The settings file name.</param>
/// <returns>Deserialized settings object of type T.</returns>
/// <exception cref="InvalidOperationException">
/// Thrown when type T is not registered in <see cref="SettingsSerializationContext"/>.
/// All settings types must be registered with <c>[JsonSerializable(typeof(T))]</c> attribute
/// for Native AOT compatibility.
/// </exception>
/// <remarks>
/// This method uses Native AOT-compatible JSON deserialization. Type T must be registered
/// in <see cref="SettingsSerializationContext"/> before calling this method.
/// </remarks>
private T GetFile<T>(string powertoyFolderName = DefaultModuleName, string fileName = DefaultFileName)
{
// Adding Trim('\0') to overcome possible NTFS file corruption.
@@ -165,8 +183,16 @@ namespace Microsoft.PowerToys.Settings.UI.Library
// The file itself did write the content correctly but something is off with the actual end of the file, hence the 0x00 bug
var jsonSettingsString = _file.ReadAllText(_settingsPath.GetSettingsPath(powertoyFolderName, fileName)).Trim('\0');
var options = _serializerOptions;
return JsonSerializer.Deserialize<T>(jsonSettingsString, options);
// For Native AOT compatibility, get JsonTypeInfo from the TypeInfoResolver
var typeInfo = _serializerOptions.TypeInfoResolver?.GetTypeInfo(typeof(T), _serializerOptions);
if (typeInfo == null)
{
throw new InvalidOperationException($"Type {typeof(T).FullName} is not registered in SettingsSerializationContext. Please add it to the [JsonSerializable] attributes.");
}
// Use AOT-friendly deserialization
return (T)JsonSerializer.Deserialize(jsonSettingsString, typeInfo)!;
}
// Save settings to a json file.

View File

@@ -27,7 +27,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
// Returns a JSON version of the class settings configuration class.
public override string ToString()
{
return JsonSerializer.Serialize(this);
return JsonSerializer.Serialize(this, SettingsSerializationContext.Default.StringProperty);
}
public static StringProperty ToStringProperty(string v)

View File

@@ -0,0 +1,127 @@
// 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.Linq;
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace CommonLibTest
{
[TestClass]
public class BasePTModuleSettingsSerializationTests
{
/// <summary>
/// Test to verify that all classes derived from BasePTModuleSettings are registered
/// in the SettingsSerializationContext for Native AOT compatibility.
/// </summary>
[TestMethod]
public void AllBasePTModuleSettingsClasses_ShouldBeRegisteredInSerializationContext()
{
// Arrange
var assembly = typeof(BasePTModuleSettings).Assembly;
var settingsClasses = assembly.GetTypes()
.Where(t => typeof(BasePTModuleSettings).IsAssignableFrom(t) && !t.IsAbstract && t != typeof(BasePTModuleSettings))
.OrderBy(t => t.Name)
.ToList();
Assert.IsTrue(settingsClasses.Count > 0, "No BasePTModuleSettings derived classes found. This test may be broken.");
var jsonSerializerOptions = new JsonSerializerOptions
{
TypeInfoResolver = SettingsSerializationContext.Default,
};
var unregisteredTypes = new System.Collections.Generic.List<string>();
// Act & Assert
foreach (var settingsType in settingsClasses)
{
var typeInfo = jsonSerializerOptions.TypeInfoResolver?.GetTypeInfo(settingsType, jsonSerializerOptions);
if (typeInfo == null)
{
unregisteredTypes.Add(settingsType.FullName ?? settingsType.Name);
}
}
// Assert
if (unregisteredTypes.Count > 0)
{
var errorMessage = $"The following {unregisteredTypes.Count} settings class(es) are NOT registered in SettingsSerializationContext:\n" +
$"{string.Join("\n", unregisteredTypes.Select(t => $" - {t}"))}\n\n" +
$"Please add [JsonSerializable(typeof(ClassName))] attribute to SettingsSerializationContext.cs for each missing type.";
Assert.Fail(errorMessage);
}
// Print success message with count
Console.WriteLine($"✓ All {settingsClasses.Count} BasePTModuleSettings derived classes are properly registered in SettingsSerializationContext.");
}
/// <summary>
/// Test to verify that calling ToJsonString() on an unregistered type throws InvalidOperationException
/// with a helpful error message.
/// </summary>
[TestMethod]
[ExpectedException(typeof(InvalidOperationException))]
public void ToJsonString_UnregisteredType_ShouldThrowInvalidOperationException()
{
// Arrange
var unregisteredSettings = new UnregisteredTestSettings
{
Name = "UnregisteredModule",
Version = "1.0.0",
};
// Act - This should throw InvalidOperationException
var jsonString = unregisteredSettings.ToJsonString();
// Assert - Exception should be thrown, so this line should never be reached
Assert.Fail("Expected InvalidOperationException was not thrown.");
}
/// <summary>
/// Test to verify that the error message for unregistered types is helpful and contains
/// necessary information for developers.
/// </summary>
[TestMethod]
public void ToJsonString_UnregisteredType_ShouldHaveHelpfulErrorMessage()
{
// Arrange
var unregisteredSettings = new UnregisteredTestSettings
{
Name = "UnregisteredModule",
Version = "1.0.0",
};
// Act & Assert
try
{
var jsonString = unregisteredSettings.ToJsonString();
Assert.Fail("Expected InvalidOperationException was not thrown.");
}
catch (InvalidOperationException ex)
{
// Verify the error message contains helpful information
Assert.IsTrue(ex.Message.Contains("UnregisteredTestSettings"), "Error message should contain the type name.");
Assert.IsTrue(ex.Message.Contains("SettingsSerializationContext"), "Error message should mention SettingsSerializationContext.");
Assert.IsTrue(ex.Message.Contains("JsonSerializable"), "Error message should mention JsonSerializable attribute.");
Console.WriteLine($"✓ Error message is helpful: {ex.Message}");
}
}
/// <summary>
/// Test class that is intentionally NOT registered in SettingsSerializationContext
/// to verify error handling for unregistered types.
/// </summary>
private sealed class UnregisteredTestSettings : BasePTModuleSettings
{
// Intentionally empty - this class should NOT be registered in SettingsSerializationContext
}
}
}

View File

@@ -2,8 +2,10 @@
// 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.Text.Json;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
using Microsoft.PowerToys.Settings.UI.UnitTests;
namespace Microsoft.PowerToys.Settings.UnitTest
{
@@ -24,5 +26,11 @@ namespace Microsoft.PowerToys.Settings.UnitTest
{
return false;
}
// Override ToJsonString to use test-specific serialization context
public override string ToJsonString()
{
return JsonSerializer.Serialize(this, TestSettingsSerializationContext.Default.BasePTSettingsTest);
}
}
}

View File

@@ -9,6 +9,7 @@ using System.Text.Json;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
using Microsoft.PowerToys.Settings.UI.UnitTests;
using Microsoft.PowerToys.Settings.UnitTest;
using Microsoft.VisualStudio.TestTools.UnitTesting;
@@ -22,7 +23,13 @@ namespace CommonLibTest
{
// Arrange
var mockFileSystem = new MockFileSystem();
var settingsUtils = new SettingsUtils(mockFileSystem);
var testSerializerOptions = new JsonSerializerOptions
{
MaxDepth = 0,
IncludeFields = true,
TypeInfoResolver = TestSettingsSerializationContext.Default,
};
var settingsUtils = new SettingsUtils(mockFileSystem, testSerializerOptions);
string file_name = "\\test";
string file_contents_correct_json_content = "{\"name\":\"powertoy module name\",\"version\":\"powertoy version\"}";
@@ -42,7 +49,13 @@ namespace CommonLibTest
{
// Arrange
var mockFileSystem = new MockFileSystem();
var settingsUtils = new SettingsUtils(mockFileSystem);
var testSerializerOptions = new JsonSerializerOptions
{
MaxDepth = 0,
IncludeFields = true,
TypeInfoResolver = TestSettingsSerializationContext.Default,
};
var settingsUtils = new SettingsUtils(mockFileSystem, testSerializerOptions);
string file_name = "test\\Test Folder";
string file_contents_correct_json_content = "{\"name\":\"powertoy module name\",\"version\":\"powertoy version\"}";

View File

@@ -0,0 +1,22 @@
// 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.Text.Json.Serialization;
using Microsoft.PowerToys.Settings.UnitTest;
namespace Microsoft.PowerToys.Settings.UI.UnitTests
{
/// <summary>
/// JSON serialization context for unit tests.
/// This context provides source-generated serialization for test-specific types.
/// </summary>
[JsonSourceGenerationOptions(
WriteIndented = false,
DefaultIgnoreCondition = JsonIgnoreCondition.Never,
IncludeFields = true)]
[JsonSerializable(typeof(BasePTSettingsTest))]
public partial class TestSettingsSerializationContext : JsonSerializerContext
{
}
}

View File

@@ -6,6 +6,7 @@ using System;
using System.Text.Json;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
using Microsoft.PowerToys.Settings.UI.UnitTests.BackwardsCompatibility;
using Microsoft.PowerToys.Settings.UI.UnitTests.Mocks;
using Microsoft.PowerToys.Settings.UI.ViewModels;
@@ -100,6 +101,22 @@ namespace ViewModelTests
mockFancyZonesSettingsUtils = ISettingsUtilsMocks.GetStubSettingsUtils<FancyZonesSettings>();
}
[TestCleanup]
public void CleanUp()
{
// Reset singleton instances to prevent state pollution between tests
ResetSettingsRepository<GeneralSettings>();
ResetSettingsRepository<FancyZonesSettings>();
}
private void ResetSettingsRepository<T>()
where T : class, ISettingsConfig, new()
{
var repositoryType = typeof(SettingsRepository<T>);
var field = repositoryType.GetField("settingsRepository", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static);
field?.SetValue(null, null);
}
[TestMethod]
public void IsEnabledShouldDisableModuleWhenSuccessful()
{

View File

@@ -10,7 +10,7 @@ using System.Text.Json.Serialization;
using System.Threading.Tasks;
using Microsoft.PowerToys.Settings.UI.Helpers;
using Microsoft.PowerToys.Settings.UI.Library;
using Settings.UI.Library;
using SettingsUILibrary = Settings.UI.Library;
namespace Microsoft.PowerToys.Settings.UI.SerializationContext;
@@ -23,7 +23,7 @@ namespace Microsoft.PowerToys.Settings.UI.SerializationContext;
[JsonSerializable(typeof(FileLocksmithSettings))]
[JsonSerializable(typeof(FindMyMouseSettings))]
[JsonSerializable(typeof(IList<PowerToysReleaseInfo>))]
[JsonSerializable(typeof(LightSwitchSettings))]
[JsonSerializable(typeof(SettingsUILibrary.LightSwitchSettings))]
[JsonSerializable(typeof(MeasureToolSettings))]
[JsonSerializable(typeof(MouseHighlighterSettings))]
[JsonSerializable(typeof(MouseJumpSettings))]

View File

@@ -638,9 +638,6 @@ Please review the placeholder content that represents the final terms and usage
<data name="AdvancedPaste_EnableAIDialogAcceptanceCheckBox.Content" xml:space="preserve">
<value>I have read and accept the information above.</value>
</data>
<data name="AdvancedPaste_EnableAdvancedAIModerationToggle.Content" xml:space="preserve">
<value>Enable OpenAI content moderation</value>
</data>
<data name="AdvancedPaste_EnablePasteAIModerationToggle.Header" xml:space="preserve">
<value>Enable OpenAI content moderation</value>
</data>
@@ -2092,9 +2089,6 @@ Made with 💗 by Microsoft and the PowerToys community.</value>
<data name="TranscodeToMp4.Header" xml:space="preserve">
<value>Transcode to .mp4 (H.264/AAC)</value>
</data>
<data name="AdvancedPaste_EnableAIDialogOpenAIApiKey.Text" xml:space="preserve">
<value>OpenAI API key:</value>
</data>
<data name="EnableAIDialog_SaveBtnText" xml:space="preserve">
<value>Save</value>
</data>
@@ -4029,7 +4023,7 @@ Activate by holding the key for the character you want to add an accent to, then
<comment>Product name: Navigation view item name for Advanced Paste</comment>
</data>
<data name="AdvancedPaste.ModuleDescription" xml:space="preserve">
<value>A tool to quickly format clipboard content into plain text, Markdown, JSON, and more. An AI-powered option requiring an OpenAI API key is available for advanced formatting.</value>
<value>Formats clipboard content into plain text, Markdown, JSON, and more. Advanced formatting can use an online or local language model endpoint.</value>
</data>
<data name="AdvancedPaste.ModuleTitle" xml:space="preserve">
<value>Advanced Paste</value>
@@ -4559,18 +4553,9 @@ Activate by holding the key for the character you want to add an accent to, then
<data name="TermsLink.Text" xml:space="preserve">
<value>OpenAI Terms</value>
</data>
<data name="AdvancedPaste_EnableAIDialog_Description.Text" xml:space="preserve">
<value>Paste with AI allows you to format your clipboard content into any format you need. Learn more about the terms of conditions while using OpenAI and privacy at Microsoft:</value>
</data>
<data name="AdvancedPaste_EnableAIDialog_LoginIntoText.Text" xml:space="preserve">
<value>• Login into your</value>
</data>
<data name="AdvancedPaste_EnableAIDialog_ConfigureOpenAIKey.Text" xml:space="preserve">
<value>Configure OpenAI key</value>
</data>
<data name="AdvancedPaste_EnableAIDialog_OpenAIApiKeysOverviewText.Text" xml:space="preserve">
<value>OpenAI API keys overview</value>
</data>
<data name="AdvancedPaste_EnableAIDialog_CreateNewKeyText.Text" xml:space="preserve">
<value>• Create a new secret key and paste it in the field below</value>
</data>
@@ -4588,9 +4573,6 @@ Activate by holding the key for the character you want to add an accent to, then
<data name="Peek_SourceCode_FontSize.Header" xml:space="preserve">
<value>Font size</value>
</data>
<data name="AdvancedPaste_EnableAIDialog_NoteAICreditsText.Text" xml:space="preserve">
<value>• NOTE: You need to have available paid credits in your OpenAI account to use this feature. If you do not have credits you will see an 'API key quota exceeded' error</value>
</data>
<data name="AdvancedPaste_EnableAIDialog_NoteAICreditsErrorText.Text" xml:space="preserve">
<value>If you do not have credits you will see an 'API key quota exceeded' error</value>
</data>

View File

@@ -12,7 +12,7 @@ Tip: Add `D:\PowerToys\tools\build` to your PATH to use the wrappers anywhere.
## When to use which
1) `build-essentials.ps1`
- Restores NuGet for `PowerToys.sln` and builds essentials (runner, settings).
- Restores NuGet for `PowerToys.slnx` and builds essentials (runner, settings).
- Auto-detects Platform; initializes VS Dev environment automatically.
- Example (PowerShell):
- `./tools/build/build-essentials.ps1`

View File

@@ -113,7 +113,7 @@ function BuildProjectsInDirectory {
$files = @()
try {
$files = Get-ChildItem -Path (Join-Path $DirectoryPath '*') -Include *.sln,*.csproj,*.vcxproj -File -ErrorAction SilentlyContinue
$files = Get-ChildItem -Path (Join-Path $DirectoryPath '*') -Include *.sln,*.slnx,*.csproj,*.vcxproj -File -ErrorAction SilentlyContinue
} catch {
$files = @()
}

View File

@@ -3,7 +3,7 @@
Build essential native PowerToys projects (runner and settings), restoring NuGet packages first.
.DESCRIPTION
Lightweight script to build a small set of essential C++ projects used by PowerToys' runner and native modules. This script first restores NuGet packages for the full solution (`PowerToys.sln`) and then builds the runner and settings projects. Intended for fast local builds during development.
Lightweight script to build a small set of essential C++ projects used by PowerToys' runner and native modules. This script first restores NuGet packages for the full solution (`PowerToys.slnx`) and then builds the runner and settings projects. Intended for fast local builds during development.
.PARAMETER Platform
Target platform for the build (for example: 'x64', 'arm64'). If omitted the script will attempt to auto-detect the host platform.
@@ -21,7 +21,7 @@ Restores packages and builds the essentials in Release mode for ARM64, even if y
.NOTES
- This script dot-sources `build-common.ps1` and uses the shared helper `RunMSBuild`.
- It will call `RestoreThenBuild 'PowerToys.sln'` before building the essential projects to ensure NuGet packages are restored.
- It will call `RestoreThenBuild 'PowerToys.slnx'` before building the essential projects to ensure NuGet packages are restored.
- The script attempts to locate the repository root automatically and can be run from any folder inside the repo.
#>
@@ -33,7 +33,7 @@ param (
# Find repository root starting from the script location
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Definition
$repoRoot = $ScriptDir
while ($repoRoot -and -not (Test-Path (Join-Path $repoRoot "PowerToys.sln"))) {
while ($repoRoot -and -not (Test-Path (Join-Path $repoRoot "PowerToys.slnx"))) {
$parent = Split-Path -Parent $repoRoot
if ($parent -eq $repoRoot) {
Write-Error "Could not find PowerToys repository root."
@@ -63,7 +63,7 @@ if (-not $Platform -or $Platform -eq '') {
}
# Ensure solution packages are restored
RestoreThenBuild 'PowerToys.sln' '' $Platform $Configuration $true
RestoreThenBuild 'PowerToys.slnx' '' $Platform $Configuration $true
# Build both runner and settings
$ProjectsToBuild = @(".\src\runner\runner.vcxproj", ".\src\settings-ui\Settings.UI\PowerToys.Settings.csproj")
@@ -71,4 +71,4 @@ $ExtraArgs = "/p:SolutionDir=$repoRoot\"
foreach ($proj in $ProjectsToBuild) {
Write-Host ("[BUILD-ESSENTIALS] Building {0}" -f $proj)
RunMSBuild $proj $ExtraArgs $Platform $Configuration
}
}

View File

@@ -73,18 +73,18 @@ $repoRoot = $scriptDir
# Navigate up from the script location to find the repo root
# Script is typically in tools\build, so go up two levels
while ($repoRoot -and -not (Test-Path (Join-Path $repoRoot "PowerToys.sln"))) {
while ($repoRoot -and -not (Test-Path (Join-Path $repoRoot "PowerToys.slnx"))) {
$parentDir = Split-Path -Parent $repoRoot
if ($parentDir -eq $repoRoot) {
# Reached the root of the drive, PowerToys.sln not found
# Reached the root of the drive, PowerToys.slnx not found
Write-Error "Could not find PowerToys repository root. Make sure this script is in the PowerToys repository."
exit 1
}
$repoRoot = $parentDir
}
if (-not $repoRoot -or -not (Test-Path (Join-Path $repoRoot "PowerToys.sln"))) {
Write-Error "Could not locate PowerToys.sln. Please ensure this script is run from within the PowerToys repository."
if (-not $repoRoot -or -not (Test-Path (Join-Path $repoRoot "PowerToys.slnx"))) {
Write-Error "Could not locate PowerToys.slnx. Please ensure this script is run from within the PowerToys repository."
exit 1
}
@@ -102,7 +102,7 @@ if (Test-Path $cmdpalOutputPath) {
$commonArgs = '/p:CIBuild=true'
# No local projects found (or continuing) - build full solution and tools
RestoreThenBuild 'PowerToys.sln' $commonArgs $Platform $Configuration
RestoreThenBuild 'PowerToys.slnx' $commonArgs $Platform $Configuration
$msixSearchRoot = Join-Path $repoRoot "$Platform\$Configuration"
$msixFiles = Get-ChildItem -Path $msixSearchRoot -Recurse -Filter *.msix |
@@ -141,10 +141,10 @@ try {
Pop-Location
}
RunMSBuild 'installer\PowerToysSetup.sln' "$commonArgs /t:restore /p:RestorePackagesConfig=true" $Platform $Configuration
RunMSBuild 'installer\PowerToysSetup.slnx' "$commonArgs /t:restore /p:RestorePackagesConfig=true" $Platform $Configuration
RunMSBuild 'installer\PowerToysSetup.sln' "$commonArgs /m /t:PowerToysInstallerVNext /p:PerUser=$PerUser" $Platform $Configuration
RunMSBuild 'installer\PowerToysSetup.slnx' "$commonArgs /m /t:PowerToysInstallerVNext /p:PerUser=$PerUser" $Platform $Configuration
RunMSBuild 'installer\PowerToysSetup.sln' "$commonArgs /m /t:PowerToysBootstrapperVNext /p:PerUser=$PerUser" $Platform $Configuration
RunMSBuild 'installer\PowerToysSetup.slnx' "$commonArgs /m /t:PowerToysBootstrapperVNext /p:PerUser=$PerUser" $Platform $Configuration
Write-Host '[PIPELINE] Completed'

View File

@@ -6,7 +6,7 @@
- The template will be available in Visual Studio, when adding a new project, under the `Visual C++` tab.
## Contributing
If you'd like to work on a PowerToy template, make required modifications to `\tools\project_template\ModuleTemplate.vcxproj` and then use the dedicated solution `PowerToyTemplate.sln` to export it as a template. Note that `ModuleTemplate.vcxproj` is actually a project template, therefore uncompilable, so we also have a dedicated `ModuleTemplateCompileTest.vcxproj` project referenced from the `PowerToys.sln` to help keeping the template sources up to date and verify it compiles correctly.
If you'd like to work on a PowerToy template, make required modifications to `\tools\project_template\ModuleTemplate.vcxproj` and then use the dedicated solution `PowerToyTemplate.sln` to export it as a template. Note that `ModuleTemplate.vcxproj` is actually a project template, therefore uncompilable, so we also have a dedicated `ModuleTemplateCompileTest.vcxproj` project referenced from the `PowerToys.slnx` to help keeping the template sources up to date and verify it compiles correctly.
## Create a new PowerToy Module
@@ -442,7 +442,7 @@ void ExamplePowertoy::save_settings() {
## Add a new PowerToy to the Installer
In the `installer` folder, open the `PowerToysSetup.sln` solution.
In the `installer` folder, open the `PowerToysSetup.slnx` solution.
Under the `PowerToysSetup` project, edit `Product.wxs`.
You will need to add a component for your module DLL. Search for `Module_ShortcutGuide` to see where to add the component declaration and where to reference that declaration so the DLL is added to the installer.
Each component requires a newly generated GUID (you can use the Visual Studio integrated tool to generate one).