Compare commits

...

11 Commits

Author SHA1 Message Date
Shawn Yuan (from Dev Box)
792c6c309a xaml format
Signed-off-by: Shawn Yuan (from Dev Box) <shuaiyuan@microsoft.com>
2025-11-06 14:01:33 +08:00
Shawn Yuan (from Dev Box)
539492bbd6 Bug fixing: AP flyout not showing selected AI
Signed-off-by: Shawn Yuan (from Dev Box) <shuaiyuan@microsoft.com>
2025-11-06 13:51:40 +08:00
Shawn Yuan (from Dev Box)
678742e141 fixed terms & privacy link issue
Signed-off-by: Shawn Yuan (from Dev Box) <shuaiyuan@microsoft.com>
2025-11-06 13:32:34 +08:00
leileizhang
fabf60d18f Add DSC v3 resource discovery support in PowerToys with subfolder structure and PATH configuration (#43253)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request

- Use DSCModules subfolder for JSON manifest files
- Use relative path "..\PowerToys.DSC.exe" in JSON manifests
- Configure PATH environment variable to point to DSCModules subfolder

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

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

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

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2025-11-06 12:08:41 +08:00
Jeremy Sinclair
1604b7b555 [Deps] Update .NET packages from 9.0.8 to 9.0.10 (#43295)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request
Updates .NET 9 Runtime / Library packages to the latest 9.0.10 servicing
release for security fixes.

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

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

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

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2025-11-06 11:33:48 +08:00
leileizhang
7fb1cdd1ea Remove PowerToys installer path from PATH to prevent some other apps crashe (#43157)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request
To support DSCv3, we previously added the PowerToys installer path to
the environment PATH. However, this change may cause some other
applications to crash.

This PR removes the installer path temporarily until we find a better
way to support DSCv3.

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

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

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

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2025-11-06 10:14:23 +08:00
leileizhang
05ae163eea Add back NuGet config file accidentally deleted by AdvancedPaste PR (#43312)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request
Deleted by this https://github.com/microsoft/PowerToys/pull/42374
<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

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

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

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2025-11-06 10:13:23 +08:00
Niels Laute
e19520e675 [CmdPal] Extension string updates (#43269)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request

Feedback from the design team:
- Reduce any redundant or long text strings for better readability and
localization
- `Verb + noun` for most extensions
- Sentence casing according to the Windows Writing Guidelines


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

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

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

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

---------

Co-authored-by: Michael Jolley <mike@baldbeardedbuilder.com>
Co-authored-by: Jiří Polášek <me@jiripolasek.com>
2025-11-05 19:29:04 -06:00
Jiří Polášek
e2591250be CmdPal: Treat System command provider as special (#43321)
## Summary of the Pull Request

This change marks the System command provider as special, ensuring
fallback items are surfaced at the top of the list.

Additionally, the SystemCommandExtensionProvider ID has been updated to
comply with the new naming convention.

As a small cleanup, SystemCommandExtensionProvider is now sealed.

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

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

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

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2025-11-05 18:34:16 -06:00
Jaylyn Barbee
e448d731f8 [Light Switch] Refactor + cleaner behavior (#43159)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request
This PR also includes a refactor to the light switch service. The
refactor will help me make cleaner updates in the future and makes the
"state" easier to track through the service.

## Validation
Completed the following steps as testing for normal behavior and edge
cases:

1. Start up
- Check defaults (delete settings.json files)
- Ensure PowerToys properly starts/does not start the module
- Ensure turning the module on and off starts/terminates the service
- Ensure the settings in the settings file are reflected in the front
end
2. Manual Override Activation
- Ensure that pressing the shortcut key triggers an update
- Ensure that pressing the shortcut key triggers a block in the schedule
(Should see logs Skipping schedule due to manual override)
- Ensure that changing windows settings triggers a manual override (at
the next minute)
- Ensure in both scenarios that the schedule is ignored until a boundary
is met
- Ensure that the schedule resumes following the boundary clearance.
4. New Day Detection / Sun Time Recalculation
- Keep service running past midnight (or simulate date change)
- Verify the last updated day is updated
- Verify new sun times are accurate and set in settings.
5. Coordinates / Mode Change
- Ensure that updates occur when the coordinates or mode changes. These
updates should reflect the new settings.
6. Schedule Mode OFF
- Turn the schedule off, check logs for notice
- Ensure the shortcut still works
- Ensure the schedule resumes as expected once turned back on.
8. Sleep / Hibernate Resume
- Set your schedule to change themes in the next 2 minutes.
- Send your machine to hibernate shutdown /h 
- Wake your machine after your theme should have changed and ensure
Light Switch catches itself up
- Repeat steps above but with a manual override triggered prior to the
theme change.Ensure manual override is flushed and schedule resumes.
9. Stop and Restart behavior
- Stop and restart the module. Ensure it behaves as expected. No reset
settings, etc.
2025-11-05 13:34:26 -05:00
Jiří Polášek
47c779e0a0 Run: handle DWM issues more gracefully (#42588)
## Summary of the Pull Request

This PR expands the scenarios where Run handles exceptions silently,
reducing the number of exception dialogs displayed to users and creating
a smoother experience. The premise remains unchanged: most exceptions
occur when Run is in the background, and the app can recover as the DWM
recovers.

This should cover most of the reports commonly encountered in the wild.

- Extended silent handling of DWM exceptions to include
`DWM_E_COMPOSITIONDISABLED` (0x80263001).
- Added silent handling for `STATUS_MESSAGE_LOST` error (NTSTATUS
0xC0000701, HR 0xD0000701).
- Implemented exception handling and retry logic for failures during
manual app theme changes (triggered by
`PowerLauncher.Helper.ThemeManager`).

I can't reproduce the error on its own; it only occurs alongside a fatal
render thread failure (`UCEERR_RENDERTHREADFAILURE`), which is
unrecoverable. Even if caught, the UI stops rendering, leaving the
end-user unable to proceed.

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

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

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

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2025-11-05 18:51:58 +01:00
68 changed files with 713 additions and 613 deletions

View File

@@ -258,6 +258,7 @@ cominterop
commandnotfound
commandpalette
compmgmt
COMPOSITIONDISABLED
COMPOSITIONFULL
CONFIGW
CONFLICTINGMODIFIERKEY

3
.gitignore vendored
View File

@@ -349,10 +349,7 @@ src/common/Telemetry/*.etl
/src/modules/powerrename/ui/RCb24464
# Generated installer file for Monaco source files.
/installer/PowerToysSetup/MonacoSRC.wxs
/installer/PowerToysSetup/DscResources.wxs
/installer/PowerToysSetupVNext/MonacoSRC.wxs
/installer/PowerToysSetupVNext/DscResources.wxs
# MSBuildCache
/MSBuildCacheLogs/

View File

@@ -65,21 +65,28 @@ if (-not (Test-Path $outputDir)) {
New-Item -Path $outputDir -ItemType Directory -Force | Out-Null
}
Write-Host "DSC manifests will be generated to: '$outputDir'"
# DSC v3 manifests go to DSCModules subfolder
$dscOutputDir = Join-Path $outputDir 'DSCModules'
if (-not (Test-Path $dscOutputDir)) {
Write-Host "Creating DSCModules subfolder at '$dscOutputDir'."
New-Item -Path $dscOutputDir -ItemType Directory -Force | Out-Null
}
Write-Host "Cleaning previously generated DSC manifest files from '$outputDir'."
Get-ChildItem -Path $outputDir -Filter 'microsoft.powertoys.*.settings.dsc.resource.json' -ErrorAction SilentlyContinue | Remove-Item -Force
Write-Host "DSC manifests will be generated to: '$dscOutputDir'"
$arguments = @('manifest', '--resource', 'settings', '--outputDir', $outputDir)
Write-Host "Cleaning previously generated DSC manifest files from '$dscOutputDir'."
Get-ChildItem -Path $dscOutputDir -Filter 'microsoft.powertoys.*.settings.dsc.resource.json' -ErrorAction SilentlyContinue | Remove-Item -Force
$arguments = @('manifest', '--resource', 'settings', '--outputDir', $dscOutputDir)
Write-Host "Invoking DSC manifest generator: '$exePath' $($arguments -join ' ')"
& $exePath @arguments
if ($LASTEXITCODE -ne 0) {
throw "PowerToys.DSC.exe exited with code $LASTEXITCODE"
}
$generatedFiles = Get-ChildItem -Path $outputDir -Filter 'microsoft.powertoys.*.settings.dsc.resource.json' -ErrorAction Stop
$generatedFiles = Get-ChildItem -Path $dscOutputDir -Filter 'microsoft.powertoys.*.settings.dsc.resource.json' -ErrorAction Stop
if ($generatedFiles.Count -eq 0) {
throw "No DSC manifest files were generated in '$outputDir'."
throw "No DSC manifest files were generated in '$dscOutputDir'."
}
Write-Host "Generated $($generatedFiles.Count) DSC manifest file(s):"

View File

@@ -34,20 +34,20 @@
<!-- Including MessagePack to force version, since it's used by StreamJsonRpc but contains vulnerabilities. After StreamJsonRpc updates the version of MessagePack, we can upgrade StreamJsonRpc instead. -->
<PackageVersion Include="MessagePack" Version="3.1.3" />
<PackageVersion Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="9.0.0" />
<PackageVersion Include="Microsoft.Data.Sqlite" Version="9.0.8" />
<PackageVersion Include="Microsoft.Data.Sqlite" Version="9.0.10" />
<!-- Including Microsoft.Bcl.AsyncInterfaces to force version, since it's used by Microsoft.SemanticKernel. -->
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="9.0.8" />
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="9.0.10" />
<PackageVersion Include="Microsoft.Windows.CppWinRT" Version="2.0.240111.5" />
<PackageVersion Include="Microsoft.Diagnostics.Tracing.TraceEvent" Version="3.1.16" />
<PackageVersion Include="Microsoft.Extensions.AI" Version="9.9.1" />
<PackageVersion Include="Microsoft.Extensions.AI.OpenAI" Version="9.9.1-preview.1.25474.6" />
<PackageVersion Include="Microsoft.Extensions.Caching.Abstractions" Version="9.0.9" />
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="9.0.9" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="9.0.9" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="9.0.9" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.9" />
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="9.0.8" />
<PackageVersion Include="Microsoft.Extensions.Hosting.WindowsServices" Version="9.0.8" />
<PackageVersion Include="Microsoft.Extensions.Caching.Abstractions" Version="9.0.10" />
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="9.0.10" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="9.0.10" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="9.0.10" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.10" />
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="9.0.10" />
<PackageVersion Include="Microsoft.Extensions.Hosting.WindowsServices" Version="9.0.10" />
<PackageVersion Include="Microsoft.AI.Foundry.Local" Version="0.3.0" />
<PackageVersion Include="Microsoft.SemanticKernel" Version="1.66.0" />
<PackageVersion Include="Microsoft.SemanticKernel.Connectors.OpenAI" Version="1.66.0" />
@@ -60,9 +60,9 @@
<PackageVersion Include="Microsoft.Toolkit.Uwp.Notifications" Version="7.1.2" />
<PackageVersion Include="Microsoft.Web.WebView2" Version="1.0.3179.45" />
<!-- Package Microsoft.Win32.SystemEvents added as a hack for being able to exclude the runtime assets so they don't conflict with 8.0.1. This is a dependency of System.Drawing.Common but the 8.0.1 version wasn't published to nuget. -->
<PackageVersion Include="Microsoft.Win32.SystemEvents" Version="9.0.8" />
<PackageVersion Include="Microsoft.Win32.SystemEvents" Version="9.0.10" />
<PackageVersion Include="Microsoft.WindowsPackageManager.ComInterop" Version="1.10.340" />
<PackageVersion Include="Microsoft.Windows.Compatibility" Version="9.0.8" />
<PackageVersion Include="Microsoft.Windows.Compatibility" Version="9.0.10" />
<PackageVersion Include="Microsoft.Windows.CsWin32" Version="0.3.183" />
<!-- CsWinRT version needs to be set to have a WinRT.Runtime.dll at the same version contained inside the NET SDK we're currently building on CI. -->
<!--
@@ -95,29 +95,29 @@
<PackageVersion Include="StreamJsonRpc" Version="2.21.69" />
<PackageVersion Include="StyleCop.Analyzers" Version="1.2.0-beta.556" />
<!-- Package System.CodeDom added as a hack for being able to exclude the runtime assets so they don't conflict with 8.0.1. This is a dependency of System.Management but the 8.0.1 version wasn't published to nuget. -->
<PackageVersion Include="System.CodeDom" Version="9.0.8" />
<PackageVersion Include="System.CodeDom" Version="9.0.10" />
<PackageVersion Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
<PackageVersion Include="System.ComponentModel.Composition" Version="9.0.8" />
<PackageVersion Include="System.Configuration.ConfigurationManager" Version="9.0.8" />
<PackageVersion Include="System.Data.OleDb" Version="9.0.8" />
<PackageVersion Include="System.ComponentModel.Composition" Version="9.0.10" />
<PackageVersion Include="System.Configuration.ConfigurationManager" Version="9.0.10" />
<PackageVersion Include="System.Data.OleDb" Version="9.0.10" />
<!-- Package System.Data.SqlClient added to force it as a dependency of Microsoft.Windows.Compatibility to the latest version available at this time. -->
<PackageVersion Include="System.Data.SqlClient" Version="4.9.0" />
<!-- Package System.Diagnostics.EventLog added as a hack for being able to exclude the runtime assets so they don't conflict with 8.0.1. This is a dependency of System.Data.OleDb but the 8.0.1 version wasn't published to nuget. -->
<PackageVersion Include="System.Diagnostics.EventLog" Version="9.0.8" />
<PackageVersion Include="System.Diagnostics.EventLog" Version="9.0.10" />
<!-- Package System.Diagnostics.PerformanceCounter added as a hack for being able to exclude the runtime assets so they don't conflict with 8.0.11. -->
<PackageVersion Include="System.Diagnostics.PerformanceCounter" Version="9.0.8" />
<PackageVersion Include="System.Diagnostics.PerformanceCounter" Version="9.0.10" />
<PackageVersion Include="System.ClientModel" Version="1.7.0" />
<PackageVersion Include="System.Drawing.Common" Version="9.0.8" />
<PackageVersion Include="System.Drawing.Common" Version="9.0.10" />
<PackageVersion Include="System.IO.Abstractions" Version="22.0.13" />
<PackageVersion Include="System.IO.Abstractions.TestingHelpers" Version="22.0.13" />
<PackageVersion Include="System.Management" Version="9.0.8" />
<PackageVersion Include="System.Management" Version="9.0.10" />
<PackageVersion Include="System.Net.Http" Version="4.3.4" />
<PackageVersion Include="System.Private.Uri" Version="4.3.2" />
<PackageVersion Include="System.Reactive" Version="6.0.1" />
<PackageVersion Include="System.Runtime.Caching" Version="9.0.8" />
<PackageVersion Include="System.ServiceProcess.ServiceController" Version="9.0.8" />
<PackageVersion Include="System.Text.Encoding.CodePages" Version="9.0.8" />
<PackageVersion Include="System.Text.Json" Version="9.0.8" />
<PackageVersion Include="System.Runtime.Caching" Version="9.0.10" />
<PackageVersion Include="System.ServiceProcess.ServiceController" Version="9.0.10" />
<PackageVersion Include="System.Text.Encoding.CodePages" Version="9.0.10" />
<PackageVersion Include="System.Text.Json" Version="9.0.10" />
<PackageVersion Include="System.Text.RegularExpressions" Version="4.3.1" />
<PackageVersion Include="UnicodeInformation" Version="2.6.0" />
<PackageVersion Include="UnitsNet" Version="5.56.0" />

View File

@@ -54,6 +54,7 @@
call cmd /C "copy ""$(ProjectDir)..\PowerToysSetupVNext\CmdPal.wxs"" ""$(ProjectDir)..\PowerToysSetupVNext\CmdPal.wxs.bk""""
call cmd /C "copy ""$(ProjectDir)..\PowerToysSetupVNext\ColorPicker.wxs"" ""$(ProjectDir)..\PowerToysSetupVNext\ColorPicker.wxs.bk""""
call cmd /C "copy ""$(ProjectDir)..\PowerToysSetupVNext\Core.wxs"" ""$(ProjectDir)..\PowerToysSetupVNext\Core.wxs.bk""""
call cmd /C "copy ""$(ProjectDir)..\PowerToysSetupVNext\DscResources.wxs"" ""$(ProjectDir)..\PowerToysSetupVNext\DscResources.wxs.bk""""
call cmd /C "copy ""$(ProjectDir)..\PowerToysSetupVNext\EnvironmentVariables.wxs"" ""$(ProjectDir)..\PowerToysSetupVNext\EnvironmentVariables.wxs.bk""""
call cmd /C "copy ""$(ProjectDir)..\PowerToysSetupVNext\FileExplorerPreview.wxs"" ""$(ProjectDir)..\PowerToysSetupVNext\FileExplorerPreview.wxs.bk""""
call cmd /C "copy ""$(ProjectDir)..\PowerToysSetupVNext\FileLocksmith.wxs"" ""$(ProjectDir)..\PowerToysSetupVNext\FileLocksmith.wxs.bk""""

View File

@@ -15,8 +15,8 @@
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
<RegistryValue Type="string" Name="powertoys_env_path_user" Value="" KeyPath="yes" />
</RegistryKey>
<!-- Append install folder to current user's PATH -->
<Environment Id="AddPowerToysToUserPath" Name="PATH" Action="set" Part="last" System="no" Value="[INSTALLFOLDER]" />
<!-- Append DSCModules folder to current user's PATH for DSC v3 usage -->
<Environment Id="AddPowerToysToUserPath" Name="PATH" Action="set" Part="last" System="no" Value="[DSCModulesReferenceFolder]" />
</Component>
<?else?>
<Component Id="powertoys_env_path_machine" Bitness="always64">
@@ -24,8 +24,8 @@
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
<RegistryValue Type="string" Name="powertoys_env_path_machine" Value="" KeyPath="yes" />
</RegistryKey>
<!-- Append install folder to machine PATH -->
<Environment Id="AddPowerToysToMachinePath" Name="PATH" Action="set" Part="last" System="yes" Value="[INSTALLFOLDER]" />
<!-- Append DSCModules folder to machine PATH for DSC v3 usage -->
<Environment Id="AddPowerToysToMachinePath" Name="PATH" Action="set" Part="last" System="yes" Value="[DSCModulesReferenceFolder]" />
</Component>
<?endif?>
<Component Id="powertoys_toast_clsid" Bitness="always64">
@@ -63,16 +63,6 @@
</Component>
</DirectoryRef>
<DirectoryRef Id="DSCModulesReferenceFolder">
<Component Id="PowerToysDSCReference" Guid="40869ACB-0BEB-4911-AE41-5E73BC1586A9" Bitness="always64">
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
<RegistryValue Type="string" Name="DSCModulesReference" Value="" KeyPath="yes" />
</RegistryKey>
<File Source="$(var.RepoDir)\src\dsc\Microsoft.PowerToys.Configure\Generated\Microsoft.PowerToys.Configure\$(var.Version).0\Microsoft.PowerToys.Configure.psd1" Id="PTConfReference.psd1" />
<File Source="$(var.RepoDir)\src\dsc\Microsoft.PowerToys.Configure\Generated\Microsoft.PowerToys.Configure\$(var.Version).0\Microsoft.PowerToys.Configure.psm1" Id="PTConfReference.psm1" />
</Component>
</DirectoryRef>
<?if $(var.PerUser) = "true" ?>
<!-- DSC module files for PerUser handled in InstallDSCModule custom action. -->
<?else?>
@@ -120,7 +110,6 @@
<RegistryValue Type="string" Name="RemoveCoreFolder" Value="" KeyPath="yes" />
</RegistryKey>
<RemoveFolder Id="RemoveBaseApplicationsAssetsFolder" Directory="BaseApplicationsAssetsFolder" On="uninstall" />
<RemoveFolder Id="RemoveDSCModulesReferenceFolder" Directory="DSCModulesReferenceFolder" On="uninstall" />
<RemoveFolder Id="RemoveWinUI3AppsInstallFolder" Directory="WinUI3AppsInstallFolder" On="uninstall" />
<RemoveFolder Id="RemoveWinUI3AppsAssetsFolder" Directory="WinUI3AppsAssetsFolder" On="uninstall" />
<RemoveFolder Id="RemoveINSTALLFOLDER" Directory="INSTALLFOLDER" On="uninstall" />
@@ -128,16 +117,15 @@
<ComponentRef Id="powertoys_exe" />
<ComponentRef Id="PowerToysStartMenuShortcut" />
<ComponentRef Id="powertoys_per_machine_comp" />
<?if $(var.PerUser) = "true" ?>
<ComponentRef Id="powertoys_env_path_user" />
<?else?>
<ComponentRef Id="powertoys_env_path_machine" />
<?endif?>
<ComponentRef Id="powertoys_toast_clsid" />
<ComponentRef Id="License_rtf" />
<ComponentRef Id="Notice_md" />
<ComponentRef Id="DesktopShortcut" />
<ComponentRef Id="PowerToysDSCReference" />
<?if $(var.PerUser) = "true" ?>
<ComponentRef Id="powertoys_env_path_user" />
<?else?>
<ComponentRef Id="powertoys_env_path_machine" />
<?endif?>
<?if $(var.PerUser) = "false" ?>
<ComponentRef Id="PowerToysDSC" />
<?endif?>

View File

@@ -0,0 +1,33 @@
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
<?include $(sys.CURRENTDIR)\Common.wxi?>
<?define DscJsonFiles=?>
<?define DscJsonFilesPath=$(var.BinDir)\DSCModules?>
<Fragment>
<DirectoryRef Id="DSCModulesReferenceFolder" FileSource="$(var.DscJsonFilesPath)">
<!-- DSC v2 PowerShell module files -->
<Component Id="PowerToysDSCReference" Guid="40869ACB-0BEB-4911-AE41-5E73BC1586A9" Bitness="always64">
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
<RegistryValue Type="string" Name="DSCModulesReference" Value="" KeyPath="yes" />
</RegistryKey>
<File Source="$(var.RepoDir)\src\dsc\Microsoft.PowerToys.Configure\Generated\Microsoft.PowerToys.Configure\$(var.Version).0\Microsoft.PowerToys.Configure.psd1" Id="PTConfReference.psd1" />
<File Source="$(var.RepoDir)\src\dsc\Microsoft.PowerToys.Configure\Generated\Microsoft.PowerToys.Configure\$(var.Version).0\Microsoft.PowerToys.Configure.psm1" Id="PTConfReference.psm1" />
</Component>
<!-- DSC v3 JSON manifest files - Generated by generateAllFileComponents.ps1 -->
<!--DscJsonFiles_Component_Def-->
</DirectoryRef>
<ComponentGroup Id="DscResourcesComponentGroup">
<ComponentRef Id="PowerToysDSCReference" />
<Component Id="RemoveDSCModulesFolder" Guid="A3C77D92-4E97-4C1A-9F2E-8B3C5D6E7F80" Directory="DSCModulesReferenceFolder">
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
<RegistryValue Type="string" Name="RemoveDSCModulesFolder" Value="" KeyPath="yes" />
</RegistryKey>
<RemoveFolder Id="RemoveDSCModulesReferenceFolder" Directory="DSCModulesReferenceFolder" On="uninstall" />
</Component>
</ComponentGroup>
</Fragment>
</Wix>

View File

@@ -14,7 +14,6 @@ SET PTRoot=$(SolutionDir)\..
call "..\..\..\publish.cmd" x64
)
call powershell.exe -NonInteractive -executionpolicy Unrestricted -File $(MSBuildThisFileDirectory)\generateMonacoWxs.ps1 -monacoWxsFile "$(MSBuildThisFileDirectory)\MonacoSRC.wxs" -Platform "$(Platform)" -nugetHeatPath "$(NUGET_PACKAGES)\wixtoolset.heat\5.0.2"
call powershell.exe -NonInteractive -executionpolicy Unrestricted -File $(MSBuildThisFileDirectory)\generateDscResourcesWxs.ps1 -dscWxsFile "$(MSBuildThisFileDirectory)\DscResources.wxs" -Platform "$(Platform)" -Configuration "$(Configuration)"
</PreBuildEvent>
</PropertyGroup>
<PropertyGroup Condition="'$(Platform)' != 'x64'">
@@ -25,7 +24,6 @@ SET PTRoot=$(SolutionDir)\..
call "..\..\..\publish.cmd" arm64
)
call powershell.exe -NonInteractive -executionpolicy Unrestricted -File $(MSBuildThisFileDirectory)\generateMonacoWxs.ps1 -monacoWxsFile "$(MSBuildThisFileDirectory)\MonacoSRC.wxs" -Platform "$(Platform)" -nugetHeatPath "$(NUGET_PACKAGES)\wixtoolset.heat\5.0.2"
call powershell.exe -NonInteractive -executionpolicy Unrestricted -File $(MSBuildThisFileDirectory)\generateDscResourcesWxs.ps1 -dscWxsFile "$(MSBuildThisFileDirectory)\DscResources.wxs" -Platform "$(Platform)" -Configuration "$(Configuration)"
</PreBuildEvent>
</PropertyGroup>
<PropertyGroup>
@@ -37,6 +35,7 @@ call powershell.exe -NonInteractive -executionpolicy Unrestricted -File $(MSBuil
call move /Y ..\..\..\CmdPal.wxs.bk ..\..\..\CmdPal.wxs
call move /Y ..\..\..\ColorPicker.wxs.bk ..\..\..\ColorPicker.wxs
call move /Y ..\..\..\Core.wxs.bk ..\..\..\Core.wxs
call move /Y ..\..\..\DscResources.wxs.bk ..\..\..\DscResources.wxs
call move /Y ..\..\..\EnvironmentVariables.wxs.bk ..\..\..\EnvironmentVariables.wxs
call move /Y ..\..\..\FileExplorerPreview.wxs.bk ..\..\..\FileExplorerPreview.wxs
call move /Y ..\..\..\FileLocksmith.wxs.bk ..\..\..\FileLocksmith.wxs

View File

@@ -317,3 +317,7 @@ Generate-FileComponents -fileListName "SettingsV2IconsModelsFiles" -wxsFilePath
#Workspaces
Generate-FileList -fileDepsJson "" -fileListName WorkspacesImagesComponentFiles -wxsFilePath $PSScriptRoot\Workspaces.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\Assets\Workspaces\"
Generate-FileComponents -fileListName "WorkspacesImagesComponentFiles" -wxsFilePath $PSScriptRoot\Workspaces.wxs
#DSC Resources - JSON manifest files in DSCModules subfolder
Generate-FileList -fileDepsJson "" -fileListName DscJsonFiles -wxsFilePath $PSScriptRoot\DscResources.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\DSCModules\"
Generate-FileComponents -fileListName "DscJsonFiles" -wxsFilePath $PSScriptRoot\DscResources.wxs

View File

@@ -1,102 +0,0 @@
[CmdletBinding()]
Param(
[Parameter(Mandatory = $True)]
[string]$dscWxsFile,
[Parameter(Mandatory = $True)]
[string]$Platform,
[Parameter(Mandatory = $True)]
[string]$Configuration
)
$ErrorActionPreference = 'Stop'
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
# Find build output directory
$buildOutputDir = Join-Path $scriptDir "..\..\$Platform\$Configuration"
if (-not (Test-Path $buildOutputDir)) {
Write-Error "Build output directory not found: '$buildOutputDir'"
exit 1
}
# Find all DSC manifest JSON files
$dscFiles = Get-ChildItem -Path $buildOutputDir -Filter "microsoft.powertoys.*.settings.dsc.resource.json" -File
if (-not $dscFiles) {
Write-Warning "No DSC manifest files found in '$buildOutputDir'"
# Create empty component group
$wxsContent = @"
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
<?include `$(sys.CURRENTDIR)\Common.wxi?>
<Fragment>
<ComponentGroup Id="DscResourcesComponentGroup">
</ComponentGroup>
</Fragment>
</Wix>
"@
Set-Content -Path $dscWxsFile -Value $wxsContent
exit 0
}
Write-Host "Found $($dscFiles.Count) DSC manifest file(s)"
# Generate WiX fragment
$wxsContent = @"
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
<?include `$(sys.CURRENTDIR)\Common.wxi?>
<Fragment>
<DirectoryRef Id="DSCModulesReferenceFolder">
"@
$componentRefs = @()
foreach ($file in $dscFiles) {
$componentId = "DscResource_" + ($file.BaseName -replace '[^A-Za-z0-9_]', '_')
$fileId = $componentId + "_File"
$guid = [System.Guid]::NewGuid().ToString().ToUpper()
$componentRefs += $componentId
$wxsContent += @"
<Component Id="$componentId" Guid="{$guid}" Directory="DSCModulesReferenceFolder">
<RegistryKey Root="`$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
<RegistryValue Type="string" Name="$componentId" Value="" KeyPath="yes"/>
</RegistryKey>
<File Id="$fileId" Source="`$(var.BinDir)$($file.Name)" Vital="no"/>
</Component>
"@
}
$wxsContent += @"
</DirectoryRef>
</Fragment>
<Fragment>
<ComponentGroup Id="DscResourcesComponentGroup">
"@
foreach ($componentId in $componentRefs) {
$wxsContent += @"
<ComponentRef Id="$componentId"/>
"@
}
$wxsContent += @"
</ComponentGroup>
</Fragment>
</Wix>
"@
# Write the WiX file
Set-Content -Path $dscWxsFile -Value $wxsContent
Write-Host "Generated DSC resources WiX fragment: '$dscWxsFile'"

View File

@@ -13,7 +13,7 @@ namespace PowerToys.DSC.Models;
public sealed class DscManifest
{
private const string Schema = "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.vscode.json";
private const string Executable = @"PowerToys.DSC.exe";
private const string Executable = @"..\PowerToys.DSC.exe";
private readonly string _type;
private readonly string _version;

View File

@@ -40,9 +40,11 @@
</EmbeddedResource>
</ItemGroup>
<!-- In debug mode, generate the DSC resource JSON files -->
<Target Name="GenerateDscResourceJsonFiles" AfterTargets="Build" Condition="'$(Configuration)' == 'Debug'">
<Message Text="Generating DSC resource JSON files inside ..." Importance="high" />
<Exec Command="dotnet &quot;$(TargetPath)&quot; manifest --resource settings --outputDir &quot;$(TargetDir)\&quot;" />
<!-- Generate the DSC resource JSON files to DSCModules subfolder -->
<!-- Skip generation in CI/CD builds (CIBuild=true) to avoid unnecessary work during pipeline -->
<Target Name="GenerateDscResourceJsonFiles" AfterTargets="Build" Condition="'$(CIBuild)' != 'true'">
<Message Text="Generating DSC resource JSON files to DSCModules subfolder..." Importance="high" />
<MakeDir Directories="$(TargetDir)DSCModules" />
<Exec Command="dotnet &quot;$(TargetPath)&quot; manifest --resource settings --outputDir &quot;$(TargetDir)DSCModules&quot;" />
</Target>
</Project>

View File

@@ -542,7 +542,10 @@
Source="{x:Bind ViewModel.ActiveAIProvider?.ServiceType, Mode=OneWay, Converter={StaticResource ServiceTypeToIconConverter}}" />
</DropDownButton.Content>
<DropDownButton.Flyout>
<Flyout Placement="Bottom" ShouldConstrainToRootBounds="False">
<Flyout
Opened="AIProviderFlyout_Opened"
Placement="Bottom"
ShouldConstrainToRootBounds="False">
<Grid
Width="386"
Margin="-4"

View File

@@ -22,6 +22,8 @@ namespace AdvancedPaste.Controls
{
public OptionsViewModel ViewModel { get; private set; }
private bool _syncingProviderSelection;
public static readonly DependencyProperty PlaceholderTextProperty = DependencyProperty.Register(
nameof(PlaceholderText),
typeof(string),
@@ -74,6 +76,11 @@ namespace AdvancedPaste.Controls
var state = ViewModel.IsBusy ? "LoadingState" : ViewModel.PasteActionError.HasText ? "ErrorState" : "DefaultState";
VisualStateManager.GoToState(this, state, true);
}
if (e.PropertyName is nameof(ViewModel.ActiveAIProvider) or nameof(ViewModel.AIProviders))
{
SyncProviderSelection();
}
}
private void ViewModel_PreviewRequested(object sender, EventArgs e)
@@ -87,6 +94,7 @@ namespace AdvancedPaste.Controls
private void Grid_Loaded(object sender, RoutedEventArgs e)
{
InputTxtBox.Focus(FocusState.Programmatic);
SyncProviderSelection();
}
[RelayCommand]
@@ -126,18 +134,56 @@ namespace AdvancedPaste.Controls
Loader.IsLoading = loading;
}
private void SyncProviderSelection()
{
if (AIProviderListView is null)
{
return;
}
try
{
_syncingProviderSelection = true;
AIProviderListView.SelectedItem = ViewModel.ActiveAIProvider;
}
finally
{
_syncingProviderSelection = false;
}
}
private void AIProviderFlyout_Opened(object sender, object e)
{
SyncProviderSelection();
}
private async void AIProviderListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (AIProviderListView.SelectedItem is PasteAIProviderDefinition provider)
if (_syncingProviderSelection)
{
if (ViewModel.SetActiveProviderCommand.CanExecute(provider))
{
await ViewModel.SetActiveProviderCommand.ExecuteAsync(provider);
}
var flyout = FlyoutBase.GetAttachedFlyout(AIProviderButton);
flyout?.Hide();
return;
}
var flyout = FlyoutBase.GetAttachedFlyout(AIProviderButton);
if (AIProviderListView.SelectedItem is not PasteAIProviderDefinition provider)
{
return;
}
if (string.Equals(ViewModel.ActiveAIProvider?.Id, provider.Id, StringComparison.OrdinalIgnoreCase))
{
flyout?.Hide();
return;
}
if (ViewModel.SetActiveProviderCommand.CanExecute(provider))
{
await ViewModel.SetActiveProviderCommand.ExecuteAsync(provider);
SyncProviderSelection();
}
flyout?.Hide();
}
}
}

View File

@@ -280,13 +280,15 @@
x:Uid="TermsLink"
Padding="0"
FontSize="12"
NavigateUri="https://openai.com/policies/terms-of-use" />
NavigateUri="{x:Bind ViewModel.TermsLinkUri, Mode=OneWay}"
Visibility="{x:Bind ViewModel.HasTermsLink, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}" />
<HyperlinkButton
x:Name="PrivacyHyperLink"
x:Uid="PrivacyLink"
Padding="0"
FontSize="12"
NavigateUri="https://openai.com/policies/privacy-policy" />
NavigateUri="{x:Bind ViewModel.PrivacyLinkUri, Mode=OneWay}"
Visibility="{x:Bind ViewModel.HasPrivacyLink, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}" />
</StackPanel>
</StackPanel>
</Flyout>

View File

@@ -71,6 +71,11 @@ namespace AdvancedPaste.ViewModels
[NotifyPropertyChangedFor(nameof(IsCustomAIAvailable))]
[NotifyPropertyChangedFor(nameof(AllowedAIProviders))]
[NotifyPropertyChangedFor(nameof(ActiveAIProvider))]
[NotifyPropertyChangedFor(nameof(ActiveAIProviderTooltip))]
[NotifyPropertyChangedFor(nameof(TermsLinkUri))]
[NotifyPropertyChangedFor(nameof(PrivacyLinkUri))]
[NotifyPropertyChangedFor(nameof(HasTermsLink))]
[NotifyPropertyChangedFor(nameof(HasPrivacyLink))]
private bool _isAllowedByGPO;
[ObservableProperty]
@@ -187,6 +192,35 @@ namespace AdvancedPaste.ViewModels
}
}
private AIServiceTypeMetadata GetActiveProviderMetadata()
{
var provider = ActiveAIProvider ?? AllowedAIProviders.FirstOrDefault();
var serviceType = provider?.ServiceTypeKind ?? AIServiceType.OpenAI;
return AIServiceTypeRegistry.GetMetadata(serviceType);
}
public Uri TermsLinkUri
{
get
{
var metadata = GetActiveProviderMetadata();
return metadata.HasTermsLink ? metadata.TermsUri : null;
}
}
public Uri PrivacyLinkUri
{
get
{
var metadata = GetActiveProviderMetadata();
return metadata.HasPrivacyLink ? metadata.PrivacyUri : null;
}
}
public bool HasTermsLink => GetActiveProviderMetadata().HasTermsLink;
public bool HasPrivacyLink => GetActiveProviderMetadata().HasPrivacyLink;
public bool ClipboardHasData => AvailableClipboardFormats != ClipboardFormat.None;
public bool ClipboardHasDataForCustomAI => PasteFormat.SupportsClipboardFormats(CustomAIFormat, AvailableClipboardFormats);
@@ -276,8 +310,8 @@ namespace AdvancedPaste.ViewModels
OnPropertyChanged(nameof(IsAdvancedAIEnabled));
OnPropertyChanged(nameof(AIProviders));
OnPropertyChanged(nameof(AllowedAIProviders));
OnPropertyChanged(nameof(ActiveAIProvider));
OnPropertyChanged(nameof(ActiveAIProviderTooltip));
NotifyActiveProviderChanged();
EnqueueRefreshPasteFormats();
}
@@ -316,8 +350,17 @@ namespace AdvancedPaste.ViewModels
}
}
NotifyActiveProviderChanged();
}
private void NotifyActiveProviderChanged()
{
OnPropertyChanged(nameof(ActiveAIProvider));
OnPropertyChanged(nameof(ActiveAIProviderTooltip));
OnPropertyChanged(nameof(TermsLinkUri));
OnPropertyChanged(nameof(PrivacyLinkUri));
OnPropertyChanged(nameof(HasTermsLink));
OnPropertyChanged(nameof(HasPrivacyLink));
}
private void RefreshPasteFormats()
@@ -837,6 +880,7 @@ namespace AdvancedPaste.ViewModels
UpdateAIProviderActiveFlags();
OnPropertyChanged(nameof(AIProviders));
NotifyActiveProviderChanged();
EnqueueRefreshPasteFormats();
}

View File

@@ -11,7 +11,6 @@
#include <logger/logger_settings.h>
#include <logger/logger.h>
#include <utils/logger_helper.h>
#include <LightSwitchServiceObserver.h>
SERVICE_STATUS g_ServiceStatus = {};
SERVICE_STATUS_HANDLE g_StatusHandle = nullptr;
@@ -20,6 +19,7 @@ extern int g_lastUpdatedDay = -1;
static ScheduleMode prevMode = ScheduleMode::Off;
static std::wstring prevLat, prevLon;
static int prevMinutes = -1;
static bool lastOverrideStatus = false;
VOID WINAPI ServiceMain(DWORD argc, LPTSTR* argv);
VOID WINAPI ServiceCtrlHandler(DWORD dwCtrl);
@@ -164,48 +164,8 @@ DWORD WINAPI ServiceWorkerThread(LPVOID lpParam)
LightSwitchSettings::instance().InitFileWatcher();
LightSwitchServiceObserver observer({ SettingId::LightTime,
SettingId::DarkTime,
SettingId::ScheduleMode,
SettingId::Sunrise_Offset,
SettingId::Sunset_Offset });
HANDLE hManualOverride = OpenEventW(SYNCHRONIZE | EVENT_MODIFY_STATE, FALSE, L"POWERTOYS_LIGHTSWITCH_MANUAL_OVERRIDE");
auto applyTheme = [](int nowMinutes, int lightMinutes, int darkMinutes, const auto& settings) {
bool isLightActive = (lightMinutes < darkMinutes) ? (nowMinutes >= lightMinutes && nowMinutes < darkMinutes) : (nowMinutes >= lightMinutes || nowMinutes < darkMinutes);
bool isSystemCurrentlyLight = GetCurrentSystemTheme();
bool isAppsCurrentlyLight = GetCurrentAppsTheme();
if (isLightActive)
{
if (settings.changeSystem && !isSystemCurrentlyLight)
{
SetSystemTheme(true);
Logger::info(L"[LightSwitchService] Changing system theme to light mode.");
}
if (settings.changeApps && !isAppsCurrentlyLight)
{
SetAppsTheme(true);
Logger::info(L"[LightSwitchService] Changing apps theme to light mode.");
}
}
else
{
if (settings.changeSystem && isSystemCurrentlyLight)
{
SetSystemTheme(false);
Logger::info(L"[LightSwitchService] Changing system theme to dark mode.");
}
if (settings.changeApps && isAppsCurrentlyLight)
{
SetAppsTheme(false);
Logger::info(L"[LightSwitchService] Changing apps theme to dark mode.");
}
}
};
LightSwitchSettings::instance().LoadSettings();
auto& settings = LightSwitchSettings::instance().settings();
@@ -213,22 +173,22 @@ DWORD WINAPI ServiceWorkerThread(LPVOID lpParam)
GetLocalTime(&st);
int nowMinutes = st.wHour * 60 + st.wMinute;
// Handle initial theme application if necessary
if (settings.scheduleMode != ScheduleMode::Off)
{
applyTheme(nowMinutes,
settings.lightTime + settings.sunrise_offset,
settings.darkTime + settings.sunset_offset,
settings);
Logger::trace(L"[LightSwitchService] Initialized g_lastUpdatedDay = {}", g_lastUpdatedDay);
Logger::info(L"[LightSwitchService] Schedule mode is set to {}. Applying theme if necessary.", settings.scheduleMode);
LightSwitchSettings::instance().ApplyThemeIfNecessary();
}
else
{
Logger::info(L"[LightSwitchService] Schedule mode is OFF - ticker suspended, waiting for manual action or mode change.");
Logger::info(L"[LightSwitchService] Schedule mode is set to Off.");
}
g_lastUpdatedDay = st.wDay;
Logger::info(L"[LightSwitchService] Initializing g_lastUpdatedDay to {}.", g_lastUpdatedDay);
ULONGLONG lastSettingsReload = 0;
// ticker loop
for (;;)
{
HANDLE waits[2] = { g_ServiceStopEvent, hParent };
@@ -237,13 +197,10 @@ DWORD WINAPI ServiceWorkerThread(LPVOID lpParam)
const auto& settings = LightSwitchSettings::instance().settings();
bool scheduleJustEnabled = (prevMode == ScheduleMode::Off && settings.scheduleMode != ScheduleMode::Off);
prevMode = settings.scheduleMode;
// ─── Handle "Schedule Off" Mode ─────────────────────────────────────────────
// If the mode is set to Off, suspend the scheduler and avoid extra work
if (settings.scheduleMode == ScheduleMode::Off)
{
Logger::info(L"[LightSwitchService] Schedule mode OFF - suspending scheduler but keeping service alive.");
Logger::info(L"[LightSwitchService] Schedule mode is OFF - suspending scheduler but keeping service alive.");
if (!hManualOverride)
hManualOverride = OpenEventW(SYNCHRONIZE | EVENT_MODIFY_STATE, FALSE, L"POWERTOYS_LIGHTSWITCH_MANUAL_OVERRIDE");
@@ -283,7 +240,6 @@ DWORD WINAPI ServiceWorkerThread(LPVOID lpParam)
{
Logger::trace(L"[LightSwitchService] Settings change event triggered, reloading settings...");
ResetEvent(LightSwitchSettings::instance().GetSettingsChangedEvent());
LightSwitchSettings::instance().LoadSettings();
const auto& newSettings = LightSwitchSettings::instance().settings();
lastSettingsReload = GetTickCount64();
@@ -298,73 +254,150 @@ DWORD WINAPI ServiceWorkerThread(LPVOID lpParam)
continue;
}
// ─── Normal Schedule Loop ───────────────────────────────────────────────────
bool scheduleJustEnabled = (prevMode == ScheduleMode::Off && settings.scheduleMode != ScheduleMode::Off);
prevMode = settings.scheduleMode;
ULONGLONG nowTick = GetTickCount64();
bool recentSettingsReload = (nowTick - lastSettingsReload < 5000);
bool recentSettingsReload = (nowTick - lastSettingsReload < 2000);
if (g_lastUpdatedDay != -1)
Logger::debug(L"[LightSwitchService] Current g_lastUpdatedDay value = {}.", g_lastUpdatedDay);
// Manual Override Detection Logic
bool manualOverrideActive = (hManualOverride && WaitForSingleObject(hManualOverride, 0) == WAIT_OBJECT_0);
if (manualOverrideActive != lastOverrideStatus)
{
bool manualOverrideActive = (hManualOverride && WaitForSingleObject(hManualOverride, 0) == WAIT_OBJECT_0);
Logger::debug(L"[LightSwitchService] Manual override active = {}", manualOverrideActive);
lastOverrideStatus = manualOverrideActive;
}
if (settings.scheduleMode != ScheduleMode::Off && !recentSettingsReload && !scheduleJustEnabled)
if (settings.scheduleMode != ScheduleMode::Off && !recentSettingsReload && !scheduleJustEnabled && !manualOverrideActive)
{
bool currentSystemTheme = GetCurrentSystemTheme();
bool currentAppsTheme = GetCurrentAppsTheme();
SYSTEMTIME st;
GetLocalTime(&st);
int nowMinutes = st.wHour * 60 + st.wMinute;
int lightBoundary = 0;
int darkBoundary = 0;
if (settings.scheduleMode == ScheduleMode::SunsetToSunrise)
{
Logger::debug(L"[LightSwitchService] Checking if manual override is active...");
bool manualOverrideActive = (hManualOverride && WaitForSingleObject(hManualOverride, 0) == WAIT_OBJECT_0);
Logger::debug(L"[LightSwitchService] Manual override active = {}", manualOverrideActive);
if (!manualOverrideActive)
{
bool currentSystemTheme = GetCurrentSystemTheme();
bool currentAppsTheme = GetCurrentAppsTheme();
SYSTEMTIME st;
GetLocalTime(&st);
int nowMinutes = st.wHour * 60 + st.wMinute;
bool shouldBeLight = (settings.lightTime < settings.darkTime) ? (nowMinutes >= settings.lightTime && nowMinutes < settings.darkTime) : (nowMinutes >= settings.lightTime || nowMinutes < settings.darkTime);
Logger::debug(L"[LightSwitchService] shouldBeLight = {}", shouldBeLight);
if ((settings.changeSystem && (currentSystemTheme != shouldBeLight)) ||
(settings.changeApps && (currentAppsTheme != shouldBeLight)))
{
Logger::debug(L"[LightSwitchService] External theme change detected - enabling manual override");
if (!hManualOverride)
{
hManualOverride = OpenEventW(SYNCHRONIZE | EVENT_MODIFY_STATE, FALSE, L"POWERTOYS_LIGHTSWITCH_MANUAL_OVERRIDE");
if (!hManualOverride)
hManualOverride = CreateEventW(nullptr, TRUE, FALSE, L"POWERTOYS_LIGHTSWITCH_MANUAL_OVERRIDE");
}
if (hManualOverride)
{
SetEvent(hManualOverride);
Logger::info(L"[LightSwitchService] Detected manual theme change outside of LightSwitch. Triggering manual override.");
skipRest = true;
}
}
}
lightBoundary = (settings.lightTime + settings.sunrise_offset) % 1440;
darkBoundary = (settings.darkTime + settings.sunset_offset) % 1440;
}
else
{
Logger::debug(L"[LightSwitchService] Skipping external-change detection (schedule off, recent reload, or just enabled).");
lightBoundary = settings.lightTime;
darkBoundary = settings.darkTime;
}
bool shouldBeLight = (lightBoundary < darkBoundary) ? (nowMinutes >= lightBoundary && nowMinutes < darkBoundary) : (nowMinutes >= lightBoundary || nowMinutes < darkBoundary);
Logger::debug(L"[LightSwitchService] shouldBeLight = {}", shouldBeLight);
bool systemMismatch = settings.changeSystem && (currentSystemTheme != shouldBeLight);
bool appsMismatch = settings.changeApps && (currentAppsTheme != shouldBeLight);
if (systemMismatch || appsMismatch)
{
// Make sure this is not because we crossed a boundary
bool crossedBoundary = false;
if (prevMinutes != -1)
{
if (nowMinutes < prevMinutes)
{
// wrapped around midnight
crossedBoundary = (prevMinutes <= lightBoundary || nowMinutes >= lightBoundary) ||
(prevMinutes <= darkBoundary || nowMinutes >= darkBoundary);
}
else
{
crossedBoundary = (prevMinutes < lightBoundary && nowMinutes >= lightBoundary) ||
(prevMinutes < darkBoundary && nowMinutes >= darkBoundary);
}
}
if (crossedBoundary)
{
Logger::info(L"[LightSwitchService] Missed boundary detected. Applying theme instead of triggering manual override.");
LightSwitchSettings::instance().ApplyThemeIfNecessary();
}
else
{
Logger::info(L"[LightSwitchService] External {} theme change detected, enabling manual override.",
systemMismatch && appsMismatch ? L"system/app" :
systemMismatch ? L"system" :
L"app");
SetEvent(hManualOverride);
skipRest = true;
}
}
}
else
{
Logger::debug(L"[LightSwitchService] Skipping external-change detection (schedule off, recent reload, or just enabled).");
}
if (hManualOverride)
manualOverrideActive = (WaitForSingleObject(hManualOverride, 0) == WAIT_OBJECT_0);
if (manualOverrideActive)
{
int lightBoundary = (settings.lightTime + settings.sunrise_offset) % 1440;
int darkBoundary = (settings.darkTime + settings.sunset_offset) % 1440;
SYSTEMTIME st;
GetLocalTime(&st);
nowMinutes = st.wHour * 60 + st.wMinute;
bool crossedLight = false;
bool crossedDark = false;
if (prevMinutes != -1)
{
// this means we are in a new day cycle
if (nowMinutes < prevMinutes)
{
crossedLight = (prevMinutes <= lightBoundary || nowMinutes >= lightBoundary);
crossedDark = (prevMinutes <= darkBoundary || nowMinutes >= darkBoundary);
}
else
{
crossedLight = (prevMinutes < lightBoundary && nowMinutes >= lightBoundary);
crossedDark = (prevMinutes < darkBoundary && nowMinutes >= darkBoundary);
}
}
if (crossedLight || crossedDark)
{
ResetEvent(hManualOverride);
Logger::info(L"[LightSwitchService] Manual override cleared after crossing schedule boundary.");
}
else
{
Logger::debug(L"[LightSwitchService] Skipping schedule due to manual override");
skipRest = true;
}
}
// ─── Apply Schedule Logic ───────────────────────────────────────────────────
// Apply theme if nothing has made us skip
if (!skipRest)
{
// Next two conditionals check for any updates necessary to the sun times.
bool modeChangedToSunset = (prevMode != settings.scheduleMode &&
settings.scheduleMode == ScheduleMode::SunsetToSunrise);
bool coordsChanged = (prevLat != settings.latitude || prevLon != settings.longitude);
if ((modeChangedToSunset || coordsChanged) && settings.scheduleMode == ScheduleMode::SunsetToSunrise)
{
Logger::info(L"[LightSwitchService] Mode or coordinates changed, recalculating sun times.");
update_sun_times(settings);
SYSTEMTIME st;
GetLocalTime(&st);
Logger::info(L"[LightSwitchService] Mode or coordinates changed, recalculating sun times.");
update_sun_times(settings);
g_lastUpdatedDay = st.wDay;
prevMode = settings.scheduleMode;
prevLat = settings.latitude;
@@ -383,70 +416,23 @@ DWORD WINAPI ServiceWorkerThread(LPVOID lpParam)
Logger::info(L"[LightSwitchService] Recalculated sun times at new day boundary.");
}
// settings after any necessary updates.
LightSwitchSettings::instance().LoadSettings();
const auto& currentSettings = LightSwitchSettings::instance().settings();
wchar_t msg[160];
swprintf_s(msg,
L"[LightSwitchService] now=%02d:%02d | light=%02d:%02d | dark=%02d:%02d | mode=%d",
L"[LightSwitchService] now=%02d:%02d | light=%02d:%02d | dark=%02d:%02d | mode=%s",
st.wHour,
st.wMinute,
currentSettings.lightTime / 60,
currentSettings.lightTime % 60,
currentSettings.darkTime / 60,
currentSettings.darkTime % 60,
static_cast<int>(currentSettings.scheduleMode));
ToString(currentSettings.scheduleMode).c_str());
Logger::info(msg);
bool manualOverrideActive = false;
if (hManualOverride)
manualOverrideActive = (WaitForSingleObject(hManualOverride, 0) == WAIT_OBJECT_0);
if (manualOverrideActive)
{
int lightBoundary = (currentSettings.lightTime + currentSettings.sunrise_offset) % 1440;
int darkBoundary = (currentSettings.darkTime + currentSettings.sunset_offset) % 1440;
bool crossedLight = false;
bool crossedDark = false;
if (prevMinutes != -1)
{
if (nowMinutes < prevMinutes)
{
crossedLight = (prevMinutes <= lightBoundary || nowMinutes >= lightBoundary);
crossedDark = (prevMinutes <= darkBoundary || nowMinutes >= darkBoundary);
}
else
{
crossedLight = (prevMinutes < lightBoundary && nowMinutes >= lightBoundary);
crossedDark = (prevMinutes < darkBoundary && nowMinutes >= darkBoundary);
}
}
Logger::debug(L"[LightSwitchService] prevMinutes={} nowMinutes={} light={} dark={}",
prevMinutes,
nowMinutes,
lightBoundary,
darkBoundary);
if (crossedLight || crossedDark)
{
ResetEvent(hManualOverride);
Logger::info(L"[LightSwitchService] Manual override cleared after crossing schedule boundary.");
}
else
{
Logger::info(L"[LightSwitchService] Skipping schedule due to manual override");
skipRest = true;
}
}
if (!skipRest)
applyTheme(nowMinutes,
currentSettings.lightTime + currentSettings.sunrise_offset,
currentSettings.darkTime + currentSettings.sunset_offset,
currentSettings);
LightSwitchSettings::instance().ApplyThemeIfNecessary();
}
// ─── Wait For Next Minute Tick Or Stop Event ────────────────────────────────
@@ -480,54 +466,6 @@ cleanup:
return 0;
}
void ApplyThemeNow()
{
LightSwitchSettings::instance().LoadSettings();
const auto& settings = LightSwitchSettings::instance().settings();
SYSTEMTIME st;
GetLocalTime(&st);
int nowMinutes = st.wHour * 60 + st.wMinute;
bool shouldBeLight = false;
if (settings.lightTime < settings.darkTime)
shouldBeLight = (nowMinutes >= settings.lightTime && nowMinutes < settings.darkTime);
else
shouldBeLight = (nowMinutes >= settings.lightTime || nowMinutes < settings.darkTime);
bool isSystemCurrentlyLight = GetCurrentSystemTheme();
bool isAppsCurrentlyLight = GetCurrentAppsTheme();
Logger::info(L"[LightSwitchService] Applying (if needed) theme immediately due to schedule change.");
if (shouldBeLight)
{
if (settings.changeSystem && !isSystemCurrentlyLight)
{
SetSystemTheme(true);
Logger::info(L"[LightSwitchService] Changing system theme to light mode.");
}
if (settings.changeApps && !isAppsCurrentlyLight)
{
SetAppsTheme(true);
Logger::info(L"[LightSwitchService] Changing apps theme to light mode.");
}
}
else
{
if (settings.changeSystem && isSystemCurrentlyLight)
{
SetSystemTheme(false);
Logger::info(L"[LightSwitchService] Changing system theme to dark mode.");
}
if (settings.changeApps && isAppsCurrentlyLight)
{
SetAppsTheme(false);
Logger::info(L"[LightSwitchService] Changing apps theme to dark mode.");
}
}
}
int APIENTRY wWinMain(HINSTANCE, HINSTANCE, PWSTR, int)
{
if (powertoys_gpo::getConfiguredLightSwitchEnabledValue() == powertoys_gpo::gpo_rule_configured_disabled)

View File

@@ -74,7 +74,6 @@
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="LightSwitchService.cpp" />
<ClCompile Include="LightSwitchServiceObserver.cpp" />
<ClCompile Include="LightSwitchSettings.cpp" />
<ClCompile Include="SettingsConstants.cpp" />
<ClCompile Include="ThemeHelper.cpp" />
@@ -85,7 +84,6 @@
<ResourceCompile Include="LightSwitchService.rc" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="LightSwitchServiceObserver.h" />
<ClInclude Include="LightSwitchSettings.h" />
<ClInclude Include="SettingsConstants.h" />
<ClInclude Include="SettingsObserver.h" />

View File

@@ -33,9 +33,6 @@
<ClCompile Include="WinHookEventIDs.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="LightSwitchServiceObserver.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="ThemeScheduler.h">
@@ -56,9 +53,6 @@
<ClInclude Include="WinHookEventIDs.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="LightSwitchServiceObserver.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<Natvis Include="$(MSBuildThisFileDirectory)..\..\natvis\wil.natvis" />

View File

@@ -1,29 +0,0 @@
#include "LightSwitchServiceObserver.h"
#include <logger.h>
#include "LightSwitchSettings.h"
// These are defined elsewhere in your service module (ServiceWorkerThread.cpp)
extern int g_lastUpdatedDay;
void ApplyThemeNow();
void LightSwitchServiceObserver::SettingsUpdate(SettingId id)
{
Logger::info(L"[LightSwitchService] Setting changed: {}", static_cast<int>(id));
g_lastUpdatedDay = -1;
ApplyThemeNow();
}
bool LightSwitchServiceObserver::WantsToBeNotified(SettingId id) const noexcept
{
switch (id)
{
case SettingId::LightTime:
case SettingId::DarkTime:
case SettingId::ScheduleMode:
case SettingId::Sunrise_Offset:
case SettingId::Sunset_Offset:
return true;
default:
return false;
}
}

View File

@@ -1,16 +0,0 @@
#pragma once
#include "SettingsObserver.h"
// The LightSwitchServiceObserver reacts when LightSwitchSettings changes.
class LightSwitchServiceObserver : public SettingsObserver
{
public:
explicit LightSwitchServiceObserver(std::unordered_set<SettingId> observedSettings) :
SettingsObserver(std::move(observedSettings))
{
}
void SettingsUpdate(SettingId id) override;
bool WantsToBeNotified(SettingId id) const noexcept override;
};

View File

@@ -2,7 +2,7 @@
#include <common/utils/json.h>
#include <common/SettingsAPI/settings_helpers.h>
#include "SettingsObserver.h"
#include "ThemeHelper.h"
#include <filesystem>
#include <fstream>
#include <WinHookEventIDs.h>
@@ -38,13 +38,80 @@ void LightSwitchSettings::InitFileWatcher()
m_settingsFileWatcher = std::make_unique<FileWatcher>(
GetSettingsFileName(),
[this]() {
Logger::info(L"[LightSwitchSettings] Settings file changed, signaling event.");
LoadSettings();
SetEvent(m_settingsChangedEvent);
using namespace std::chrono;
{
std::lock_guard<std::mutex> lock(m_debounceMutex);
m_lastChangeTime = steady_clock::now();
if (m_debouncePending)
return;
m_debouncePending = true;
}
m_debounceThread = std::jthread([this](std::stop_token stop) {
using namespace std::chrono;
while (!stop.stop_requested())
{
std::this_thread::sleep_for(seconds(3));
auto elapsed = steady_clock::now() - m_lastChangeTime;
if (elapsed >= seconds(1))
break;
}
{
std::lock_guard<std::mutex> lock(m_debounceMutex);
m_debouncePending = false;
}
Logger::info(L"[LightSwitchSettings] Settings file stabilized, reloading.");
try
{
LoadSettings();
ApplyThemeIfNecessary();
SetEvent(m_settingsChangedEvent);
}
catch (const std::exception& e)
{
std::wstring wmsg;
wmsg.assign(e.what(), e.what() + strlen(e.what()));
Logger::error(L"[LightSwitchSettings] Exception during debounced reload: {}", wmsg);
}
});
});
}
}
LightSwitchSettings::~LightSwitchSettings()
{
Logger::info(L"[LightSwitchSettings] Cleaning up settings resources...");
// Stop and join the debounce thread (std::jthread auto-joins, but we can signal stop too)
if (m_debounceThread.joinable())
{
m_debounceThread.request_stop();
}
// Release the file watcher so it closes file handles and background threads
if (m_settingsFileWatcher)
{
m_settingsFileWatcher.reset();
Logger::info(L"[LightSwitchSettings] File watcher stopped.");
}
// Close the Windows event handle
if (m_settingsChangedEvent)
{
CloseHandle(m_settingsChangedEvent);
m_settingsChangedEvent = nullptr;
Logger::info(L"[LightSwitchSettings] Settings changed event closed.");
}
Logger::info(L"[LightSwitchSettings] Cleanup complete.");
}
void LightSwitchSettings::AddObserver(SettingsObserver& observer)
{
m_observers.insert(&observer);
@@ -73,6 +140,7 @@ HANDLE LightSwitchSettings::GetSettingsChangedEvent() const
void LightSwitchSettings::LoadSettings()
{
std::lock_guard<std::mutex> guard(m_settingsMutex);
try
{
PowerToysSettings::PowerToyValues values =
@@ -181,4 +249,49 @@ void LightSwitchSettings::LoadSettings()
{
// Keeps defaults if load fails
}
}
void LightSwitchSettings::ApplyThemeIfNecessary()
{
std::lock_guard<std::mutex> guard(m_settingsMutex);
SYSTEMTIME st;
GetLocalTime(&st);
int nowMinutes = st.wHour * 60 + st.wMinute;
bool shouldBeLight = false;
if (m_settings.lightTime < m_settings.darkTime)
shouldBeLight = (nowMinutes >= m_settings.lightTime && nowMinutes < m_settings.darkTime);
else
shouldBeLight = (nowMinutes >= m_settings.lightTime || nowMinutes < m_settings.darkTime);
bool isSystemCurrentlyLight = GetCurrentSystemTheme();
bool isAppsCurrentlyLight = GetCurrentAppsTheme();
if (shouldBeLight)
{
if (m_settings.changeSystem && !isSystemCurrentlyLight)
{
SetSystemTheme(true);
Logger::info(L"[LightSwitchService] Changing system theme to light mode.");
}
if (m_settings.changeApps && !isAppsCurrentlyLight)
{
SetAppsTheme(true);
Logger::info(L"[LightSwitchService] Changing apps theme to light mode.");
}
}
else
{
if (m_settings.changeSystem && isSystemCurrentlyLight)
{
SetSystemTheme(false);
Logger::info(L"[LightSwitchService] Changing system theme to dark mode.");
}
if (m_settings.changeApps && isAppsCurrentlyLight)
{
SetAppsTheme(false);
Logger::info(L"[LightSwitchService] Changing apps theme to dark mode.");
}
}
}

View File

@@ -5,7 +5,10 @@
#include <vector>
#include <memory>
#include <windows.h>
#include <mutex>
#include <atomic>
#include <thread>
#include <chrono>
#include <common/SettingsAPI/FileWatcher.h>
#include <common/SettingsAPI/settings_objects.h>
#include <SettingsConstants.h>
@@ -78,12 +81,13 @@ public:
void RemoveObserver(SettingsObserver& observer);
void LoadSettings();
void ApplyThemeIfNecessary();
HANDLE GetSettingsChangedEvent() const;
private:
LightSwitchSettings();
~LightSwitchSettings() = default;
~LightSwitchSettings();
LightSwitchConfig m_settings;
std::unique_ptr<FileWatcher> m_settingsFileWatcher;
@@ -92,4 +96,11 @@ private:
void NotifyObservers(SettingId id) const;
HANDLE m_settingsChangedEvent = nullptr;
mutable std::mutex m_settingsMutex;
// Debounce state
std::atomic_bool m_debouncePending{ false };
std::mutex m_debounceMutex;
std::chrono::steady_clock::time_point m_lastChangeTime{};
std::jthread m_debounceThread;
};

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<clear />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
</packageSources>
<packageSourceMapping>
<packageSource key="nuget.org">
<package pattern="*" />
</packageSource>
</packageSourceMapping>
</configuration>

View File

@@ -11,7 +11,7 @@ namespace Microsoft.CmdPal.UI.ViewModels.BuiltinCommands;
/// <summary>
/// Built-in Provider for a top-level command which can quit the application. Invokes the <see cref="QuitCommand"/>, which sends a <see cref="QuitMessage"/>.
/// </summary>
public partial class BuiltInsCommandProvider : CommandProvider
public sealed partial class BuiltInsCommandProvider : CommandProvider
{
private readonly OpenSettingsCommand openSettings = new();
private readonly QuitCommand quitCommand = new();
@@ -21,7 +21,7 @@ public partial class BuiltInsCommandProvider : CommandProvider
public override ICommandItem[] TopLevelCommands() =>
[
new CommandItem(openSettings) { Subtitle = Properties.Resources.builtin_open_settings_subtitle },
new CommandItem(openSettings) { },
new CommandItem(_newExtension) { Title = _newExtension.Title, Subtitle = Properties.Resources.builtin_new_extension_subtitle },
];
@@ -34,7 +34,7 @@ public partial class BuiltInsCommandProvider : CommandProvider
public BuiltInsCommandProvider()
{
Id = "Core";
Id = "com.microsoft.cmdpal.builtin.core";
DisplayName = Properties.Resources.builtin_display_name;
Icon = IconHelpers.FromRelativePath("Assets\\StoreLogo.scale-200.png");
}

View File

@@ -30,7 +30,12 @@ public partial class MainListPage : DynamicListPage,
{
private readonly string[] _specialFallbacks = [
"com.microsoft.cmdpal.builtin.run",
"com.microsoft.cmdpal.builtin.calculator"
"com.microsoft.cmdpal.builtin.calculator",
"com.microsoft.cmdpal.builtin.system",
"com.microsoft.cmdpal.builtin.core",
"com.microsoft.cmdpal.builtin.websearch",
"com.microsoft.cmdpal.builtin.windowssettings",
"com.microsoft.cmdpal.builtin.datetime",
];
private readonly IServiceProvider _serviceProvider;

View File

@@ -117,11 +117,8 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="builtin_open_settings_subtitle" xml:space="preserve">
<value>Open Command Palette settings</value>
</data>
<data name="builtin_new_extension_subtitle" xml:space="preserve">
<value>Creates a project for a new Command Palette extension</value>
<value>Generate a new Command Palette extension project</value>
</data>
<data name="builtin_quit_subtitle" xml:space="preserve">
<value>Exit Command Palette</value>
@@ -157,10 +154,10 @@
<value>Open</value>
</data>
<data name="builtin_create_extension_title" xml:space="preserve">
<value>Create a new extension</value>
<value>Create extension</value>
</data>
<data name="builtin_open_settings_name" xml:space="preserve">
<value>Open Settings</value>
<value>Open Command Palette settings</value>
</data>
<data name="builtin_create_extension_success" xml:space="preserve">
<value>Successfully created your new extension!</value>

View File

@@ -25,7 +25,7 @@ public class QueryTests : CommandPaletteUnitTestBase
[DataRow("hibernate", "Hibernate")]
[DataRow("open recycle", "Open Recycle Bin")]
[DataRow("empty recycle", "Empty Recycle Bin")]
[DataRow("uefi", "UEFI Firmware Settings")]
[DataRow("uefi", "UEFI firmware settings")]
public void TopLevelPageQueryTest(string input, string matchedTitle)
{
var settings = new Settings();
@@ -143,6 +143,6 @@ public class QueryTests : CommandPaletteUnitTestBase
Assert.IsNotNull(result);
var firstItem = result.FirstOrDefault();
var firstItemIsUefiCommand = firstItem?.Title.Contains("UEFI", StringComparison.OrdinalIgnoreCase) ?? false;
Assert.AreEqual(hasCommand, firstItemIsUefiCommand, $"Expected to match (or not match) 'UEFI Firmware Settings' but got '{firstItem?.Title}'");
Assert.AreEqual(hasCommand, firstItemIsUefiCommand, $"Expected to match (or not match) 'UEFI firmware settings' but got '{firstItem?.Title}'");
}
}

View File

@@ -41,7 +41,7 @@ namespace Microsoft.CmdPal.Ext.TimeDate.UnitTests
// Assert
Assert.IsNotNull(provider);
Assert.IsNotNull(provider.DisplayName);
Assert.AreEqual("DateTime", provider.Id);
Assert.AreEqual("com.microsoft.cmdpal.builtin.datetime", provider.Id);
Assert.IsNotNull(provider.Icon);
Assert.IsNotNull(provider.Settings);
}
@@ -103,7 +103,7 @@ namespace Microsoft.CmdPal.Ext.TimeDate.UnitTests
// Assert
Assert.IsFalse(string.IsNullOrEmpty(subtitle));
Assert.IsTrue(subtitle.Contains("Provides time and date values in different formats"));
Assert.IsTrue(subtitle.Contains("Show time and date values in different formats"));
}
}
}

View File

@@ -16,7 +16,7 @@ public class WebSearchCommandProviderTests
var provider = new WebSearchCommandsProvider();
// Assert
Assert.AreEqual("WebSearch", provider.Id);
Assert.AreEqual("com.microsoft.cmdpal.builtin.websearch", provider.Id);
}
[TestMethod]

View File

@@ -49,8 +49,8 @@ public class BasicTests : CommandPaletteTestBase
{
SetSearchBox("time and date");
var searchFileItem = this.Find<NavigationViewItem>("Time and Date");
Assert.AreEqual(searchFileItem.Name, "Time and Date");
var searchFileItem = this.Find<NavigationViewItem>("Time and date");
Assert.AreEqual(searchFileItem.Name, "Time and date");
searchFileItem.DoubleClick();
SetTimeAndDaterExtensionSearchBox("year");
@@ -63,8 +63,8 @@ public class BasicTests : CommandPaletteTestBase
{
SetSearchBox("Windows Terminal");
var searchFileItem = this.Find<NavigationViewItem>("Open Windows Terminal Profiles");
Assert.AreEqual(searchFileItem.Name, "Open Windows Terminal Profiles");
var searchFileItem = this.Find<NavigationViewItem>("Open Windows Terminal profiles");
Assert.AreEqual(searchFileItem.Name, "Open Windows Terminal profiles");
searchFileItem.DoubleClick();
// SetSearchBox("PowerShell");
@@ -74,10 +74,10 @@ public class BasicTests : CommandPaletteTestBase
[TestMethod]
public void BasicWindowsSettingsTest()
{
SetSearchBox("Windows Settings");
SetSearchBox("Windows settings");
var searchFileItem = this.Find<NavigationViewItem>("Windows Settings");
Assert.AreEqual(searchFileItem.Name, "Windows Settings");
var searchFileItem = this.Find<NavigationViewItem>("Windows settings");
Assert.AreEqual(searchFileItem.Name, "Windows settings");
searchFileItem.DoubleClick();
SetSearchBox("power");

View File

@@ -35,7 +35,6 @@ public partial class AllAppsCommandProvider : CommandProvider
_listItem = new(_page)
{
Subtitle = Resources.search_installed_apps,
MoreCommands = [new CommandContextItem(AllAppsSettings.Instance.Settings.SettingsPage)],
};

View File

@@ -19,7 +19,7 @@ namespace Microsoft.CmdPal.Ext.Apps.Properties {
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "18.0.0.0")]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources {
@@ -61,7 +61,7 @@ namespace Microsoft.CmdPal.Ext.Apps.Properties {
}
/// <summary>
/// Looks up a localized string similar to All Apps.
/// Looks up a localized string similar to Search apps.
/// </summary>
internal static string all_apps {
get {
@@ -313,16 +313,7 @@ namespace Microsoft.CmdPal.Ext.Apps.Properties {
}
/// <summary>
/// Looks up a localized string similar to Search installed apps.
/// </summary>
internal static string search_installed_apps {
get {
return ResourceManager.GetString("search_installed_apps", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Search installed apps....
/// Looks up a localized string similar to Search apps....
/// </summary>
internal static string search_installed_apps_placeholder {
get {

View File

@@ -124,14 +124,11 @@
<data name="installed_apps" xml:space="preserve">
<value>Installed apps</value>
</data>
<data name="search_installed_apps" xml:space="preserve">
<value>Search installed apps</value>
</data>
<data name="all_apps" xml:space="preserve">
<value>All Apps</value>
<value>Search apps</value>
</data>
<data name="search_installed_apps_placeholder" xml:space="preserve">
<value>Search installed apps...</value>
<value>Search apps...</value>
</data>
<data name="open_path_in_console" xml:space="preserve">
<value>Open path in console</value>

View File

@@ -120,7 +120,6 @@ internal sealed partial class FallbackOpenFileItem : FallbackCommandItem, System
var indexerPage = new IndexerPage(query, _searchEngine, _queryCookie, results);
Title = string.Format(CultureInfo.CurrentCulture, fallbackItemSearchPageTitleCompositeFormat, query);
Icon = Icons.FileExplorerIcon;
Subtitle = Resources.Indexer_Subtitle;
Command = indexerPage;
return;

View File

@@ -33,7 +33,6 @@ public partial class IndexerCommandsProvider : CommandProvider
new CommandItem(new IndexerPage())
{
Title = Resources.Indexer_Title,
Subtitle = Resources.Indexer_Subtitle,
}
];
}

View File

@@ -68,7 +68,6 @@ internal sealed partial class IndexerPage : DynamicListPage, IDisposable
_noSearchEmptyContent = new CommandItem(new NoOpCommand())
{
Icon = Icon,
Title = Resources.Indexer_Subtitle,
Subtitle = Resources.Indexer_NoSearchQueryMessageTip,
};

View File

@@ -295,15 +295,6 @@ namespace Microsoft.CmdPal.Ext.Indexer.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to Search files on this device.
/// </summary>
internal static string Indexer_Subtitle {
get {
return ResourceManager.GetString("Indexer_Subtitle", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Search files.
/// </summary>

View File

@@ -171,9 +171,6 @@
<data name="Indexer_Settings_FallbackCommand_FilePathExist" xml:space="preserve">
<value>Only when file path exist</value>
</data>
<data name="Indexer_Subtitle" xml:space="preserve">
<value>Search files on this device</value>
</data>
<data name="Indexer_Title" xml:space="preserve">
<value>Search files</value>
</data>

View File

@@ -25,8 +25,7 @@ public partial class RegistryCommandsProvider : CommandProvider
return [
new CommandItem(new RegistryListPage(_settingsManager))
{
Title = "Registry",
Subtitle = "Navigate the Windows registry",
Title = "Browse the Windows registry",
}
];
}

View File

@@ -97,7 +97,7 @@ namespace Microsoft.CmdPal.Ext.Shell.Properties {
}
/// <summary>
/// Looks up a localized string similar to Executes commands (e.g. &apos;ping&apos;, &apos;cmd&apos;).
/// Looks up a localized string similar to Execute system commands like &apos;ping&apos; and &apos;cmd&apos;.
/// </summary>
public static string cmd_plugin_description {
get {

View File

@@ -121,7 +121,7 @@
<value>Run commands</value>
</data>
<data name="cmd_plugin_description" xml:space="preserve">
<value>Executes commands (e.g. 'ping', 'cmd')</value>
<value>Execute system commands like 'ping' and 'cmd'</value>
</data>
<data name="cmd_has_been_executed_times" xml:space="preserve">
<value>this command has been executed {0} times</value>

View File

@@ -160,7 +160,7 @@ namespace Microsoft.CmdPal.Ext.System {
}
/// <summary>
/// Looks up a localized string similar to Adapter Details.
/// Looks up a localized string similar to Adapter details.
/// </summary>
public static string Microsoft_plugin_ext_adapter_details {
get {
@@ -169,7 +169,7 @@ namespace Microsoft.CmdPal.Ext.System {
}
/// <summary>
/// Looks up a localized string similar to Connection Details.
/// Looks up a localized string similar to Connection details.
/// </summary>
public static string Microsoft_plugin_ext_connection_details {
get {
@@ -187,7 +187,7 @@ namespace Microsoft.CmdPal.Ext.System {
}
/// <summary>
/// Looks up a localized string similar to Open System Command.
/// Looks up a localized string similar to Open system command.
/// </summary>
public static string Microsoft_plugin_ext_fallback_display_title {
get {
@@ -205,7 +205,7 @@ namespace Microsoft.CmdPal.Ext.System {
}
/// <summary>
/// Looks up a localized string similar to System Commands.
/// Looks up a localized string similar to System commands.
/// </summary>
public static string Microsoft_plugin_ext_system_page_name {
get {
@@ -214,7 +214,7 @@ namespace Microsoft.CmdPal.Ext.System {
}
/// <summary>
/// Looks up a localized string similar to Windows System Commands.
/// Looks up a localized string similar to Windows system commands.
/// </summary>
public static string Microsoft_plugin_ext_system_page_title {
get {
@@ -628,7 +628,7 @@ namespace Microsoft.CmdPal.Ext.System {
}
/// <summary>
/// Looks up a localized string similar to You are about to restart this computer, are you sure?.
/// Looks up a localized string similar to You are about to restart this computer. Are you sure?.
/// </summary>
public static string Microsoft_plugin_sys_restart_computer_confirmation {
get {
@@ -790,7 +790,7 @@ namespace Microsoft.CmdPal.Ext.System {
}
/// <summary>
/// Looks up a localized string similar to DNS Suffix.
/// Looks up a localized string similar to DNS suffix.
/// </summary>
public static string Microsoft_plugin_sys_Suffix {
get {
@@ -817,7 +817,7 @@ namespace Microsoft.CmdPal.Ext.System {
}
/// <summary>
/// Looks up a localized string similar to UEFI Firmware Settings.
/// Looks up a localized string similar to UEFI firmware settings.
/// </summary>
public static string Microsoft_plugin_sys_uefi {
get {
@@ -826,7 +826,7 @@ namespace Microsoft.CmdPal.Ext.System {
}
/// <summary>
/// Looks up a localized string similar to You are about to reboot this computer into UEFI Firmware Settings menu, are you sure?.
/// Looks up a localized string similar to You are about to reboot this computer into UEFI firmware settings menu, are you sure?.
/// </summary>
public static string Microsoft_plugin_sys_uefi_confirmation {
get {
@@ -835,7 +835,7 @@ namespace Microsoft.CmdPal.Ext.System {
}
/// <summary>
/// Looks up a localized string similar to Reboot computer into UEFI Firmware Settings (Requires administrative permissions.).
/// Looks up a localized string similar to Reboot computer into UEFI firmware Settings (requires administrative permissions.).
/// </summary>
public static string Microsoft_plugin_sys_uefi_description {
get {

View File

@@ -149,10 +149,10 @@
<value>Shutdown</value>
</data>
<data name="Microsoft_plugin_ext_connection_details" xml:space="preserve">
<value>Connection Details</value>
<value>Connection details</value>
</data>
<data name="Microsoft_plugin_ext_adapter_details" xml:space="preserve">
<value>Adapter Details</value>
<value>Adapter details</value>
</data>
<data name="Microsoft_plugin_ext_copy" xml:space="preserve">
<value>Copy to clipboard</value>
@@ -161,10 +161,10 @@
<value>Hide disconnected network info</value>
</data>
<data name="Microsoft_plugin_ext_system_page_title" xml:space="preserve">
<value>Windows System Commands</value>
<value>Windows system commands</value>
</data>
<data name="Microsoft_plugin_ext_system_page_name" xml:space="preserve">
<value>System Commands</value>
<value>System commands</value>
</data>
<data name="Microsoft_plugin_sys_AdapterName" xml:space="preserve">
<value>Adapter name</value>
@@ -327,7 +327,7 @@
<comment>This should align to the action in Windows of a restarting your computer.</comment>
</data>
<data name="Microsoft_plugin_sys_restart_computer_confirmation" xml:space="preserve">
<value>You are about to restart this computer, are you sure?</value>
<value>You are about to restart this computer. Are you sure?</value>
<comment>This should align to the action in Windows of a restarting your computer.</comment>
</data>
<data name="Microsoft_plugin_sys_restart_computer_description" xml:space="preserve">
@@ -381,7 +381,7 @@
<value>State</value>
</data>
<data name="Microsoft_plugin_sys_Suffix" xml:space="preserve">
<value>DNS Suffix</value>
<value>DNS suffix</value>
</data>
<data name="Microsoft_plugin_sys_TunnelConnection" xml:space="preserve">
<value>Tunnel</value>
@@ -391,15 +391,15 @@
<comment>Means type like category. Here it means network interface type (ethernet, wifi, ...).</comment>
</data>
<data name="Microsoft_plugin_sys_uefi" xml:space="preserve">
<value>UEFI Firmware Settings</value>
<value>UEFI firmware settings</value>
<comment>This should align to the action in Windows Recovery Environment that restart into uefi settings.</comment>
</data>
<data name="Microsoft_plugin_sys_uefi_confirmation" xml:space="preserve">
<value>You are about to reboot this computer into UEFI Firmware Settings menu, are you sure?</value>
<value>You are about to reboot this computer into UEFI firmware settings menu, are you sure?</value>
<comment>This should align to the action in Windows Recovery Environment that restart into uefi settings.</comment>
</data>
<data name="Microsoft_plugin_sys_uefi_description" xml:space="preserve">
<value>Reboot computer into UEFI Firmware Settings (Requires administrative permissions.)</value>
<value>Reboot computer into UEFI firmware Settings (requires administrative permissions.)</value>
<comment>This should align to the action in Windows Recovery Environment that restart into uefi settings.</comment>
</data>
<data name="Microsoft_plugin_sys_Unknown" xml:space="preserve">
@@ -415,7 +415,7 @@
<value>Sleep</value>
</data>
<data name="Microsoft_plugin_ext_fallback_display_title" xml:space="preserve">
<value>Open System Command</value>
<value>Execute system commands</value>
</data>
<data name="Microsoft_plugin_sys_RestartShell" xml:space="preserve">
<value>Restart Windows Explorer</value>

View File

@@ -9,7 +9,7 @@ using Microsoft.CommandPalette.Extensions.Toolkit;
namespace Microsoft.CmdPal.Ext.System;
public partial class SystemCommandExtensionProvider : CommandProvider
public sealed partial class SystemCommandExtensionProvider : CommandProvider
{
private readonly ICommandItem[] _commands;
private static readonly SettingsManager _settingsManager = new();
@@ -19,7 +19,7 @@ public partial class SystemCommandExtensionProvider : CommandProvider
public SystemCommandExtensionProvider()
{
DisplayName = Resources.Microsoft_plugin_ext_system_page_name;
Id = "System";
Id = "com.microsoft.cmdpal.builtin.system";
_commands = [
new CommandItem(Page)
{

View File

@@ -17,11 +17,6 @@ public sealed partial class TimeDateCalculator
/// </summary>
private const string InputDelimiter = "::";
/// <summary>
/// A list of conjunctions that we ignore on search
/// </summary>
private static readonly string[] _conjunctionList = Resources.Microsoft_plugin_timedate_Search_ConjunctionList.Split("; ");
/// <summary>
/// Searches for results
/// </summary>

View File

@@ -349,7 +349,7 @@ namespace Microsoft.CmdPal.Ext.TimeDate {
}
/// <summary>
/// Looks up a localized string similar to Time and Date.
/// Looks up a localized string similar to Time and date.
/// </summary>
public static string Microsoft_plugin_timedate_main_page_title {
get {
@@ -448,7 +448,7 @@ namespace Microsoft.CmdPal.Ext.TimeDate {
}
/// <summary>
/// Looks up a localized string similar to Provides time and date values in different formats.
/// Looks up a localized string similar to Show time and date values in different formats.
/// </summary>
public static string Microsoft_plugin_timedate_plugin_description {
get {
@@ -484,7 +484,7 @@ namespace Microsoft.CmdPal.Ext.TimeDate {
}
/// <summary>
/// Looks up a localized string similar to Time and Date.
/// Looks up a localized string similar to Time and date.
/// </summary>
public static string Microsoft_plugin_timedate_plugin_name {
get {
@@ -501,15 +501,6 @@ namespace Microsoft.CmdPal.Ext.TimeDate {
}
}
/// <summary>
/// Looks up a localized string similar to "for; and; nor; but; or; so".
/// </summary>
public static string Microsoft_plugin_timedate_Search_ConjunctionList {
get {
return ResourceManager.GetString("Microsoft_plugin_timedate_Search_ConjunctionList", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Date and time; Time and Date; Custom format.
/// </summary>

View File

@@ -202,7 +202,7 @@
<comment>'UTC' means here 'Universal Time Convention'</comment>
</data>
<data name="Microsoft_plugin_timedate_plugin_description" xml:space="preserve">
<value>Provides time and date values in different formats</value>
<value>Show time and date values in different formats</value>
<comment>Do not translate the placeholders like '{0}' because it will be replaced in code.</comment>
</data>
<data name="Microsoft_plugin_timedate_plugin_description_example_calendarWeek" xml:space="preserve">
@@ -215,7 +215,7 @@
<value>Time</value>
</data>
<data name="Microsoft_plugin_timedate_plugin_name" xml:space="preserve">
<value>Time and Date</value>
<value>Time and date</value>
</data>
<data name="Microsoft_plugin_timedate_Rfc1123" xml:space="preserve">
<value>RFC1123</value>
@@ -252,10 +252,6 @@
<value>Current Time; Now</value>
<comment>Don't change order</comment>
</data>
<data name="Microsoft_plugin_timedate_Search_ConjunctionList" xml:space="preserve">
<value>for; and; nor; but; or; so</value>
<comment>List of conjunctions. We don't add 'yet' because this can be a synonym of 'now' which might be problematic on localized searches.</comment>
</data>
<data name="Microsoft_plugin_timedate_Second" xml:space="preserve">
<value>Second</value>
</data>
@@ -358,7 +354,7 @@
<value>Error: Invalid input</value>
</data>
<data name="Microsoft_plugin_timedate_main_page_title" xml:space="preserve">
<value>Time and Date</value>
<value>Time and date</value>
</data>
<data name="Microsoft_plugin_timedate_InvalidInput_SupportedInput" xml:space="preserve">
<value>A {0}format name{0}, a {0}valid date or time value{0}, or a {0}prefixed number{0}. To search for a format in a specific date/time please use the syntax {0}format::date/time/number{0}.{1}Supported prefixes:{2}'{0}u{0}' for Unix Timestamp{2}'{0}ums{0}' for Unix Timestamp in milliseconds{2}'{0}ft{0}' for Windows file time{2}'{0}oa{0}' for OLE Automation Date{2}'{0}exc{0}' for Excel's 1900 date value{2}'{0}exf{0}' for Excel's 1904 date value</value>

View File

@@ -12,7 +12,7 @@ using Microsoft.CommandPalette.Extensions.Toolkit;
namespace Microsoft.CmdPal.Ext.TimeDate;
public partial class TimeDateCommandsProvider : CommandProvider
public sealed partial class TimeDateCommandsProvider : CommandProvider
{
private readonly CommandItem _command;
private static readonly SettingsManager _settingsManager = new SettingsManager();
@@ -23,7 +23,7 @@ public partial class TimeDateCommandsProvider : CommandProvider
public TimeDateCommandsProvider()
{
DisplayName = Resources.Microsoft_plugin_timedate_plugin_name;
Id = "DateTime";
Id = "com.microsoft.cmdpal.builtin.datetime";
_command = new CommandItem(_timeDateExtensionPage)
{
Icon = _timeDateExtensionPage.Icon,

View File

@@ -11,7 +11,7 @@ using Microsoft.CommandPalette.Extensions.Toolkit;
namespace Microsoft.CmdPal.Ext.WebSearch;
public partial class WebSearchCommandsProvider : CommandProvider
public sealed partial class WebSearchCommandsProvider : CommandProvider
{
private readonly SettingsManager _settingsManager = new();
private readonly FallbackExecuteSearchItem _fallbackItem;
@@ -22,7 +22,7 @@ public partial class WebSearchCommandsProvider : CommandProvider
public WebSearchCommandsProvider()
{
Id = "WebSearch";
Id = "com.microsoft.cmdpal.builtin.websearch";
DisplayName = Resources.extension_name;
Icon = Icons.WebSearch;
Settings = _settingsManager.Settings;

View File

@@ -124,16 +124,7 @@ namespace Microsoft.CmdPal.Ext.WinGet.Properties {
}
/// <summary>
/// Looks up a localized string similar to Search for extensions on WinGet.
/// </summary>
public static string winget_install_extensions_subtitle {
get {
return ResourceManager.GetString("winget_install_extensions_subtitle", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Install Command Palette extensions.
/// Looks up a localized string similar to Add Command Palette extensions from WinGet.
/// </summary>
public static string winget_install_extensions_title {
get {
@@ -205,7 +196,7 @@ namespace Microsoft.CmdPal.Ext.WinGet.Properties {
}
/// <summary>
/// Looks up a localized string similar to Search WinGet.
/// Looks up a localized string similar to Find apps on WinGet.
/// </summary>
public static string winget_page_name {
get {
@@ -268,7 +259,7 @@ namespace Microsoft.CmdPal.Ext.WinGet.Properties {
}
/// <summary>
/// Looks up a localized string similar to Search for extensions in the Store.
/// Looks up a localized string similar to Add Command Palette extensions from the Microsoft Store.
/// </summary>
public static string winget_search_store_title {
get {

View File

@@ -127,19 +127,15 @@
<comment></comment>
</data>
<data name="winget_install_extensions_title" xml:space="preserve">
<value>Install Command Palette extensions</value>
<comment></comment>
</data>
<data name="winget_install_extensions_subtitle" xml:space="preserve">
<value>Search for extensions on WinGet</value>
<value>Add Command Palette extensions from WinGet</value>
<comment></comment>
</data>
<data name="winget_search_store_title" xml:space="preserve">
<value>Search for extensions in the Store</value>
<value>Add Command Palette extensions from the Microsoft Store</value>
<comment></comment>
</data>
<data name="winget_page_name" xml:space="preserve">
<value>Search WinGet</value>
<value>Find apps on WinGet</value>
<comment></comment>
</data>
<data name="winget_create_catalog_error" xml:space="preserve">

View File

@@ -27,7 +27,6 @@ public partial class WinGetExtensionCommandsProvider : CommandProvider
new WinGetExtensionPage(WinGetExtensionPage.ExtensionsTag) { Title = Properties.Resources.winget_install_extensions_title })
{
Title = Properties.Resources.winget_install_extensions_title,
Subtitle = Properties.Resources.winget_install_extensions_subtitle,
},
new ListItem(

View File

@@ -70,7 +70,7 @@ namespace Microsoft.CmdPal.Ext.WindowWalker.Properties {
}
/// <summary>
/// Looks up a localized string similar to On all Desktops.
/// Looks up a localized string similar to On all desktops.
/// </summary>
public static string VirtualDesktopHelper_AllDesktops {
get {
@@ -196,7 +196,7 @@ namespace Microsoft.CmdPal.Ext.WindowWalker.Properties {
}
/// <summary>
/// Looks up a localized string similar to Not Responding.
/// Looks up a localized string similar to Not responding.
/// </summary>
public static string windowwalker_NotResponding {
get {

View File

@@ -209,7 +209,7 @@
<value>When disabled, windows will be sorted by title</value>
</data>
<data name="windowwalker_NotResponding" xml:space="preserve">
<value>Not Responding</value>
<value>Not responding</value>
</data>
<data name="window_walker_top_level_command_title" xml:space="preserve">
<value>Switch between open windows</value>
@@ -218,7 +218,7 @@
<value>Switch to</value>
</data>
<data name="VirtualDesktopHelper_AllDesktops" xml:space="preserve">
<value>On all Desktops</value>
<value>On all desktops</value>
</data>
<data name="VirtualDesktopHelper_Desktop" xml:space="preserve">
<value>Desktop {0}</value>

View File

@@ -23,8 +23,7 @@ public partial class WindowsServicesCommandsProvider : CommandProvider
return [
new CommandItem(new ServicesListPage())
{
Title = "Windows Services",
Subtitle = "Manage Windows Services",
Title = "Manage Windows services",
}
];
}

View File

@@ -10,7 +10,7 @@ using Microsoft.CommandPalette.Extensions.Toolkit;
namespace Microsoft.CmdPal.Ext.WindowsSettings;
public partial class WindowsSettingsCommandsProvider : CommandProvider
public sealed partial class WindowsSettingsCommandsProvider : CommandProvider
{
private readonly CommandItem _searchSettingsListItem;
@@ -22,7 +22,7 @@ public partial class WindowsSettingsCommandsProvider : CommandProvider
public WindowsSettingsCommandsProvider()
{
Id = "Windows.Settings";
Id = "com.microsoft.cmdpal.builtin.windowssettings";
DisplayName = Resources.WindowsSettingsProvider_DisplayName;
Icon = Icons.WindowsSettingsIcon;

View File

@@ -97,7 +97,7 @@ namespace Microsoft.CmdPal.Ext.WindowsTerminal.Properties {
}
/// <summary>
/// Looks up a localized string similar to Open Windows Terminal Profiles.
/// Looks up a localized string similar to Open Windows Terminal profiles.
/// </summary>
internal static string list_item_title {
get {

View File

@@ -156,7 +156,7 @@
<value>Settings</value>
</data>
<data name="list_item_title" xml:space="preserve">
<value>Open Windows Terminal Profiles</value>
<value>Open Windows Terminal profiles</value>
</data>
<data name="preferred_channel" xml:space="preserve">
<value>Preferred channel</value>

View File

@@ -74,7 +74,7 @@ namespace Microsoft.PowerToys.Run.Plugin.System.UnitTests
var result = main.Object.Query(expectedQuery).FirstOrDefault().SubTitle;
// Assert
Assert.AreEqual("Reboot computer into UEFI Firmware Settings (Requires administrative permissions.)", result);
Assert.AreEqual("Reboot computer into UEFI firmware settings (Requires administrative permissions.)", result);
}
[TestMethod]

View File

@@ -19,7 +19,7 @@ namespace Microsoft.PowerToys.Run.Plugin.System.Properties {
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "18.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources {
@@ -682,7 +682,7 @@ namespace Microsoft.PowerToys.Run.Plugin.System.Properties {
}
/// <summary>
/// Looks up a localized string similar to You are about to reboot this computer into UEFI Firmware Settings menu, are you sure?.
/// Looks up a localized string similar to You are about to reboot this computer into UEFI firmware settings menu, are you sure?.
/// </summary>
internal static string Microsoft_plugin_sys_uefi_confirmation {
get {
@@ -691,7 +691,7 @@ namespace Microsoft.PowerToys.Run.Plugin.System.Properties {
}
/// <summary>
/// Looks up a localized string similar to Reboot computer into UEFI Firmware Settings (Requires administrative permissions.).
/// Looks up a localized string similar to Reboot computer into UEFI firmware settings (Requires administrative permissions.).
/// </summary>
internal static string Microsoft_plugin_sys_uefi_description {
get {

View File

@@ -362,15 +362,15 @@
<comment>Means type like category. Here it means network interface type (ethernet, wifi, ...).</comment>
</data>
<data name="Microsoft_plugin_sys_uefi" xml:space="preserve">
<value>UEFI Firmware Settings</value>
<value>UEFI firmware settings</value>
<comment>This should align to the action in Windows Recovery Environment that restart into uefi settings.</comment>
</data>
<data name="Microsoft_plugin_sys_uefi_confirmation" xml:space="preserve">
<value>You are about to reboot this computer into UEFI Firmware Settings menu, are you sure?</value>
<value>You are about to reboot this computer into UEFI firmware settings menu, are you sure?</value>
<comment>This should align to the action in Windows Recovery Environment that restart into uefi settings.</comment>
</data>
<data name="Microsoft_plugin_sys_uefi_description" xml:space="preserve">
<value>Reboot computer into UEFI Firmware Settings (Requires administrative permissions.)</value>
<value>Reboot computer into UEFI firmware settings (Requires administrative permissions.)</value>
<comment>This should align to the action in Windows Recovery Environment that restart into uefi settings.</comment>
</data>
<data name="Microsoft_plugin_sys_Unknown" xml:space="preserve">

View File

@@ -208,7 +208,7 @@
<comment>'UTC' means here 'Universal Time Convention'</comment>
</data>
<data name="Microsoft_plugin_timedate_plugin_description" xml:space="preserve">
<value>Provides time and date values for the system time or a custom time stamp (e.g.'{0}', '{1}', '{2}', '{3}')</value>
<value>Shows time and date values for the system time or a custom time stamp (e.g.'{0}', '{1}', '{2}', '{3}')</value>
<comment>Do not translate the placeholders like '{0}' because it will be replaced in code.</comment>
</data>
<data name="Microsoft_plugin_timedate_plugin_description_example_calendarWeek" xml:space="preserve">

View File

@@ -61,7 +61,7 @@ namespace PowerLauncher.Helper
// Many bug reports because users see the "Report problem UI" after "the" crash with System.Runtime.InteropServices.COMException 0xD0000701 or 0x80263001.
// However, displaying this "Report problem UI" during WPF crashes, especially when DWM composition is changing, is not ideal; some users reported it hangs for up to a minute before the "Report problem UI" appears.
// This change modifies the behavior to log the exception instead of showing the "Report problem UI".
if (IsDwmCompositionException(e as System.Runtime.InteropServices.COMException))
if (ExceptionHelper.IsRecoverableDwmCompositionException(e as System.Runtime.InteropServices.COMException))
{
var logger = LogManager.GetLogger(LoggerName);
logger.Error($"From {(isNotUIThread ? "non" : string.Empty)} UI thread's exception: {ExceptionFormatter.FormatException(e)}");
@@ -91,22 +91,5 @@ namespace PowerLauncher.Helper
}
}
}
private static bool IsDwmCompositionException(System.Runtime.InteropServices.COMException comException)
{
if (comException == null)
{
return false;
}
var stackTrace = comException.StackTrace;
if (string.IsNullOrEmpty(stackTrace))
{
return false;
}
// Check for common DWM composition changed patterns in the stack trace
return stackTrace.Contains("DwmCompositionChanged");
}
}
}

View File

@@ -0,0 +1,46 @@
// 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.Runtime.InteropServices;
namespace PowerLauncher.Helper
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:Field names should not contain underscore", Justification = "Win32 naming conventions")]
internal static class ExceptionHelper
{
private const string PresentationFrameworkExceptionSource = "PresentationFramework";
private const int DWM_E_COMPOSITIONDISABLED = unchecked((int)0x80263001);
// HRESULT for NT STATUS STATUS_MESSAGE_LOST (0xC0000701 | 0x10000000 == 0xD0000701)
private const int STATUS_MESSAGE_LOST_HR = unchecked((int)0xD0000701);
/// <summary>
/// Returns true if the exception is a recoverable DWM composition exception.
/// </summary>
internal static bool IsRecoverableDwmCompositionException(Exception exception)
{
if (exception is not COMException comException)
{
return false;
}
if (comException.HResult is DWM_E_COMPOSITIONDISABLED)
{
return true;
}
if (comException.HResult is STATUS_MESSAGE_LOST_HR && comException.Source == PresentationFrameworkExceptionSource)
{
return true;
}
// Check for common DWM composition changed patterns in the stack trace
var stackTrace = comException.StackTrace;
return !string.IsNullOrEmpty(stackTrace) &&
stackTrace.Contains("DwmCompositionChanged");
}
}
}

View File

@@ -3,13 +3,16 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media;
using ManagedCommon;
using Microsoft.Win32;
using Wox.Infrastructure.Image;
using Wox.Infrastructure.UserSettings;
using Wox.Plugin.Logger;
namespace PowerLauncher.Helper
{
@@ -20,6 +23,9 @@ namespace PowerLauncher.Helper
private readonly ThemeHelper _themeHelper = new();
private bool _disposed;
private CancellationTokenSource _themeUpdateTokenSource;
private const int MaxRetries = 5;
private const int InitialDelayMs = 2000;
public Theme CurrentTheme { get; private set; }
@@ -108,10 +114,80 @@ namespace PowerLauncher.Helper
{
Theme newTheme = _themeHelper.DetermineTheme(_settings.Theme);
_mainWindow.Dispatcher.Invoke(() =>
// Cancel any existing theme update operation
_themeUpdateTokenSource?.Cancel();
_themeUpdateTokenSource?.Dispose();
_themeUpdateTokenSource = new CancellationTokenSource();
// Start theme update with retry logic in the background
_ = UpdateThemeWithRetryAsync(newTheme, _themeUpdateTokenSource.Token);
}
/// <summary>
/// Applies the theme with retry logic for desktop composition errors.
/// </summary>
/// <param name="theme">The theme to apply.</param>
/// <param name="cancellationToken">Token to cancel the operation.</param>
private async Task UpdateThemeWithRetryAsync(Theme theme, CancellationToken cancellationToken)
{
var delayMs = 0;
const int maxAttempts = MaxRetries + 1;
for (var attempt = 1; attempt <= maxAttempts; attempt++)
{
SetSystemTheme(newTheme);
});
try
{
if (delayMs > 0)
{
await Task.Delay(delayMs, cancellationToken);
}
if (cancellationToken.IsCancellationRequested)
{
Log.Debug("Theme update operation was cancelled.", typeof(ThemeManager));
return;
}
await _mainWindow.Dispatcher.InvokeAsync(() =>
{
SetSystemTheme(theme);
});
if (attempt > 1)
{
Log.Info($"Successfully applied theme after {attempt - 1} retry attempt(s).", typeof(ThemeManager));
}
return;
}
catch (COMException ex) when (ExceptionHelper.IsRecoverableDwmCompositionException(ex))
{
switch (attempt)
{
case 1:
Log.Warn($"Desktop composition is disabled (HRESULT: 0x{ex.HResult:X}). Scheduling retries for theme update.", typeof(ThemeManager));
delayMs = InitialDelayMs;
break;
case < maxAttempts:
Log.Warn($"Retry {attempt - 1}/{MaxRetries} failed: Desktop composition still disabled. Retrying in {delayMs * 2}ms...", typeof(ThemeManager));
delayMs *= 2;
break;
default:
Log.Exception($"Failed to set theme after {MaxRetries} retry attempts. Desktop composition remains disabled.", ex, typeof(ThemeManager));
break;
}
}
catch (OperationCanceledException)
{
Log.Debug("Theme update operation was cancelled.", typeof(ThemeManager));
return;
}
catch (Exception ex)
{
Log.Exception($"Unexpected error during theme update (attempt {attempt}/{maxAttempts}): {ex.Message}", ex, typeof(ThemeManager));
throw;
}
}
}
public void Dispose()
@@ -130,6 +206,8 @@ namespace PowerLauncher.Helper
if (disposing)
{
SystemEvents.UserPreferenceChanged -= OnUserPreferenceChanged;
_themeUpdateTokenSource?.Cancel();
_themeUpdateTokenSource?.Dispose();
}
_disposed = true;

View File

@@ -36,9 +36,9 @@ public static class AIServiceTypeRegistry
IsOnlineService = true,
LegalDescription = "AdvancedPaste_Anthropic_LegalDescription",
TermsLabel = "AdvancedPaste_Anthropic_TermsLabel",
TermsUri = new Uri("https://www.anthropic.com/legal/terms-of-service"),
TermsUri = new Uri("https://privacy.claude.com/en/collections/10672567-policies-terms-of-service"),
PrivacyLabel = "AdvancedPaste_Anthropic_PrivacyLabel",
PrivacyUri = new Uri("https://www.anthropic.com/legal/privacy"),
PrivacyUri = new Uri("https://privacy.claude.com/en/"),
},
[AIServiceType.AzureAIInference] = new AIServiceTypeMetadata
{
@@ -81,9 +81,9 @@ public static class AIServiceTypeRegistry
IsOnlineService = true,
LegalDescription = "AdvancedPaste_Google_LegalDescription",
TermsLabel = "AdvancedPaste_Google_TermsLabel",
TermsUri = new Uri("https://policies.google.com/terms"),
TermsUri = new Uri("https://ai.google.dev/gemini-api/terms"),
PrivacyLabel = "AdvancedPaste_Google_PrivacyLabel",
PrivacyUri = new Uri("https://policies.google.com/privacy"),
PrivacyUri = new Uri("https://support.google.com/gemini/answer/13594961"),
},
[AIServiceType.HuggingFace] = new AIServiceTypeMetadata
{
@@ -126,9 +126,9 @@ public static class AIServiceTypeRegistry
IsLocalModel = true,
LegalDescription = "AdvancedPaste_LocalModel_LegalDescription",
TermsLabel = "AdvancedPaste_Ollama_TermsLabel",
TermsUri = new Uri("https://ollama.com/terms"),
TermsUri = new Uri("https://ollama.org/terms"),
PrivacyLabel = "AdvancedPaste_Ollama_PrivacyLabel",
PrivacyUri = new Uri("https://ollama.com/privacy"),
PrivacyUri = new Uri("https://ollama.org/privacy"),
},
[AIServiceType.Onnx] = new AIServiceTypeMetadata
{

View File

@@ -93,21 +93,28 @@ if (-not (Test-Path $outputDir)) {
New-Item -Path $outputDir -ItemType Directory -Force | Out-Null
}
Write-Host "DSC manifests will be generated to: '$outputDir'"
# DSC v3 manifests go to DSCModules subfolder
$dscOutputDir = Join-Path $outputDir 'DSCModules'
if (-not (Test-Path $dscOutputDir)) {
Write-Host "Creating DSCModules subfolder at '$dscOutputDir'."
New-Item -Path $dscOutputDir -ItemType Directory -Force | Out-Null
}
Write-Host "Cleaning previously generated DSC manifest files from '$outputDir'."
Get-ChildItem -Path $outputDir -Filter 'microsoft.powertoys.*.settings.dsc.resource.json' -ErrorAction SilentlyContinue | Remove-Item -Force
Write-Host "DSC manifests will be generated to: '$dscOutputDir'"
$arguments = @('manifest', '--resource', 'settings', '--outputDir', $outputDir)
Write-Host "Cleaning previously generated DSC manifest files from '$dscOutputDir'."
Get-ChildItem -Path $dscOutputDir -Filter 'microsoft.powertoys.*.settings.dsc.resource.json' -ErrorAction SilentlyContinue | Remove-Item -Force
$arguments = @('manifest', '--resource', 'settings', '--outputDir', $dscOutputDir)
Write-Host "Invoking DSC manifest generator: '$exePath' $($arguments -join ' ')"
& $exePath @arguments
if ($LASTEXITCODE -ne 0) {
throw "PowerToys.DSC.exe exited with code $LASTEXITCODE"
}
$generatedFiles = Get-ChildItem -Path $outputDir -Filter 'microsoft.powertoys.*.settings.dsc.resource.json' -ErrorAction Stop
$generatedFiles = Get-ChildItem -Path $dscOutputDir -Filter 'microsoft.powertoys.*.settings.dsc.resource.json' -ErrorAction Stop
if ($generatedFiles.Count -eq 0) {
throw "No DSC manifest files were generated in '$outputDir'."
throw "No DSC manifest files were generated in '$dscOutputDir'."
}
Write-Host "Generated $($generatedFiles.Count) DSC manifest file(s):"