mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-01-16 17:26:22 +01:00
Compare commits
11 Commits
shawn/AddI
...
leilzh/fix
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b94bf4fdae | ||
|
|
2d18303f0a | ||
|
|
cdf66a70e9 | ||
|
|
9dcddfd4b8 | ||
|
|
503bcbdf2d | ||
|
|
52f2561937 | ||
|
|
dc30f3fd8e | ||
|
|
8f9a2c32cc | ||
|
|
bcd1583bb7 | ||
|
|
abf5e5bf8d | ||
|
|
e1e74b8fab |
3
.github/actions/spell-check/expect.txt
vendored
3
.github/actions/spell-check/expect.txt
vendored
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
3396
PowerToys.sln
3396
PowerToys.sln
File diff suppressed because it is too large
Load Diff
1041
PowerToys.slnx
Normal file
1041
PowerToys.slnx
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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`).
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -92,7 +92,7 @@ The module’s 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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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
|
||||
22
installer/PowerToysSetup.slnx
Normal file
22
installer/PowerToysSetup.slnx
Normal 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>
|
||||
@@ -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>;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -558,6 +558,7 @@ public class UWPApplication : IUWPApplication
|
||||
IsPackaged = true,
|
||||
Commands = app.GetCommands(),
|
||||
AppIdentifier = app.GetAppIdentifier(),
|
||||
PackageFamilyName = app.Package.FamilyName,
|
||||
};
|
||||
return item;
|
||||
}
|
||||
|
||||
@@ -1065,6 +1065,7 @@ public class Win32Program : IProgram
|
||||
DirPath = app.Location,
|
||||
Commands = app.GetCommands(),
|
||||
AppIdentifier = app.GetAppIdentifier(),
|
||||
FullExecutablePath = app.FullPath,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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(",")) {
|
||||
|
||||
@@ -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>(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
|
||||
public string ToJsonString()
|
||||
{
|
||||
return JsonSerializer.Serialize(this);
|
||||
return JsonSerializer.Serialize(this, SettingsSerializationContext.Default.ImageResizerProperties);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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\"}";
|
||||
|
||||
|
||||
@@ -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
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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))]
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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 = @()
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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).
|
||||
|
||||
Reference in New Issue
Block a user