Compare commits

...

16 Commits

Author SHA1 Message Date
Shawn Yuan (from Dev Box)
12e9832dd0 update
Signed-off-by: Shawn Yuan (from Dev Box) <shuaiyuan@microsoft.com>
2025-11-12 11:55:00 +08:00
Shawn Yuan (from Dev Box)
da176a6fc8 remove debug launching
Signed-off-by: Shawn Yuan (from Dev Box) <shuaiyuan@microsoft.com>
2025-11-12 11:13:23 +08:00
Kai Tao
5e3e0660e7 Revert hybrid crt to make powertoys quit safely (#43484)
<!-- 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
After enabling Hybrid CRT, the PowerToys process failed to properly
unload some module interface DLLs when quit application. The root cause
is still unclear, but this change reverts the behavior to ensure safe
unloading.

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

- [ ] Closes: #43413
- [ ] **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
Can unload dll safely when quit powertoys
2025-11-12 10:39:21 +08:00
Jaylyn Barbee
29688cea0e [Light Switch] Enter latitude and longitude manually in Sunrise to sunset mode (#43276)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request
This PR introduces new UI to allow the users to manually enter their
lat/long.

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

- [x] Closes: #42429
- [x] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [x] **Localization:** All end-user-facing strings can be localized
- [x] **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:
#[5979](https://github.com/MicrosoftDocs/windows-dev-docs-pr/pull/5979)

---------

Co-authored-by: Niels Laute <niels.laute@live.nl>
2025-11-11 16:18:18 -05:00
Alex Mihaiuc
a0f33c8af1 Make zooming animation smooth for ZoomIt on mouse move (#42339)
This change coalesces the `WM_MOUSEMOVE` events with the `WM_TIMER`
events in the cases 1, 2 (telescope animation for zoom in, respectively,
out). I factored into a new lambda the `WM_TIMER` code and I made it
save the "now" amount of ticks into the new `g_TelescopingZoomLastTick`.

During `WM_MOUSEMOVE` processing, if the current tick count (rather
imprecise, but good enough) is larger than the animation step,
`ZOOM_LEVEL_STEP_TIME`, then I call that lambda. It's convenient that
the code in there already handles the "end of telescope zooming
animation" case.

## Validation Steps Performed

The choppy zooming animation was evident while moving the mouse during a
telescope zoom. It was also [reported on the Sysinternals
blog](https://techcommunity.microsoft.com/blog/sysinternals-blog/zoomit-v9-10-procdump-3-5-for-linux-and-jcd-1-0-1/4461244/replies/4461281).
2025-11-11 09:42:59 -06:00
leileizhang
e1edcc13b7 Fix the issue where the InstallLocation is missing after upgrading (#43462)
<!-- 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
**Root Cause**

The issue occurs because the Installed property in MSI indicates whether
the current product code has been installed on the machine.
During a Major Upgrade (where the old and new versions have different
ProductCode values), the installation sequence is:
1. Burn schedules uninstall of the old MSI (the old version might not
have this custom action).
2. Then installs the new MSI.
> - At this point, the current product has not yet been installed, so
the Installed property is empty.
> - The condition NOT Installed evaluates to true, and
SetBundleInstallLocationData / SetBundleInstallLocation executes
normally, writing the InstallLocation registry key.

This means upgrading from an older version without this function to a
newer version with it works correctly.
However, issues appear in subsequent upgrades (from “newer” → “newer”)
because both versions include the same custom action.
The previous condition restricted the action to run only on first
install, preventing it from running during upgrades.

**Fix**

Added `OR WIX_UPGRADE_DETECTED` to the condition of
SetBundleInstallLocationData / SetBundleInstallLocation custom actions,
so they also execute during upgrade scenarios, ensuring the
InstallLocation registry value is correctly updated after version
upgrades.

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

- [x] Closes: #43451
- [ ] **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-11 20:07:58 +08:00
leileizhang
756feb9f8c Clean up unused providers for AdvancedPaste (#43433)
<!-- 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
Clean up unused providers for AdvancedPaste

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

- [x] Closes: #43429
- [ ] **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-11 20:07:41 +08:00
leileizhang
2fff688c6f [CI] Fix Win10 New+ build: add local Generated Files include path (#43461)
<!-- 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 (#43361) tries to fix the CI failure, and the root cause is
correct. However, using ;$(MSBuildThisFileDirectory)Generated Files
causes the include path to become \Generated Files\Generated
Files\resource.h. Removing Generated Files fixes the issue so that cl
can find the correct file.

<!-- 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-11 20:07:26 +08:00
Kai Tao
24d7ae54ce Find my mouse: Fix the issue find my mouse xaml island could not be run in x64 environment (#43420)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request
This pull request introduces a configuration update to the
`src/runner/runner.vcxproj` project file. The change enables undocked,
registration-free WinRT initialization for the Windows App SDK, which
can improve compatibility and reduce dependency issues in certain
deployment scenarios.

Windows App SDK configuration:

* Enabled `WindowsAppSdkUndockedRegFreeWinRTInitialize` to allow
registration-free WinRT initialization in the project file
`runner.vcxproj`.

<!-- 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
Validate in x64 environment, and it can be run without problem, no
errors any more
2025-11-11 09:34:50 +08:00
Mario Hewardt
a271a2f8af Enable GIF support for ZoomIt (#43266)
Closes #43265
2025-11-10 13:57:13 -06:00
Michael Jolley
a33c484c93 CmdPal: Removing app tag from apps (#43439)
The "App" tag is pretty redundant and is adding a lot of visual noise.
Closes #38968

New:

![image](https://github.com/user-attachments/assets/5eff195c-5aaf-477a-b988-4b3b14583569)
2025-11-10 20:03:36 +01:00
Niels Laute
f20c3b832b Asset updates (#43431)
title
2025-11-10 19:04:16 +01:00
Mike Griese
9c2884ab41 CmdPal: Bump version to 0.7 (#43428)
title
2025-11-10 10:38:55 -06:00
Jiří Polášek
076828f592 CmdPal: Remove duplicate HideWindow call in MainWindow .ctor (#43360)
## Summary of the Pull Request

Refer to the title. Calling this here will just log an error. Another
HideWindow is at the end of the constructor, so the behavior remains
unchanged.

<!-- 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-10 10:38:22 -06:00
Niels Laute
301d504db1 • instead of . in README (#43353)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request

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

- [ ] Closes: #xxx
- [ ] **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-10 06:18:27 -06:00
moooyo
e4a8488a2a [PowerRename] Fix FuzzTest project configuration issue (#43299)
<!-- 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 add the missing configuration for FuzzTest to avoid build issue
in local env.

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

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

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

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

Co-authored-by: Yu Leng <yuleng@microsoft.com>
2025-11-10 11:40:39 +08:00
93 changed files with 2627 additions and 1313 deletions

View File

@@ -47,7 +47,6 @@ Allmodule
ALLOWUNDO
ALLVIEW
ALPHATYPE
amazonbedrock
AModifier
amr
ANDSCANS
@@ -66,6 +65,7 @@ APIIs
Apm
APPBARDATA
APPEXECLINK
appext
APPLICATIONFRAMEHOST
appmanifest
APPMODEL
@@ -187,6 +187,7 @@ CAPTUREBLT
CAPTURECHANGED
CARETBLINKING
CAtl
CBN
cch
CCHDEVICENAME
CCHFORMNAME
@@ -414,6 +415,9 @@ DNLEN
DONOTROUND
DONTVALIDATEPATH
dotnet
downsampled
downsampling
Downsampled
downscale
DPICHANGED
DPIs
@@ -598,6 +602,7 @@ getfilesiginforedist
geolocator
GETHOTKEY
GETICON
GETLBTEXT
GETMINMAXINFO
GETNONCLIENTMETRICS
GETPROPERTYSTOREFLAGS
@@ -605,6 +610,7 @@ GETSCREENSAVERRUNNING
GETSECKEY
GETSTICKYKEYS
GETTEXTLENGTH
GIFs
gitmodules
GHND
GMEM
@@ -615,6 +621,7 @@ GPOCA
gpp
gpu
gradians
grctlext
Gridcustomlayout
GSM
gtm
@@ -688,7 +695,6 @@ hmonitor
homies
homljgmgpmcbpjbnjpfijnhipfkiclkd
HOOKPROC
huggingface
HORZRES
HORZSIZE
Hostbackdropbrush

View File

@@ -1,56 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0"
xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- Hybrid CRT configuration -->
<PropertyGroup Condition="'$(HybridCrtConfiguration)'==''">
<HybridCrtConfiguration>$(Configuration)</HybridCrtConfiguration>
</PropertyGroup>
<!-- Skip Hybrid CRT for AppContainer/UWP projects as they require MultiThreadedDLL -->
<PropertyGroup Condition="'$(AppContainerApplication)'=='true' and '$(_VC_Target_Library_Platform)'!='Desktop'">
<HybridCrtConfiguration></HybridCrtConfiguration>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(HybridCrtConfiguration)'=='Debug'">
<ClCompile>
<!-- We use MultiThreadedDebug, rather than MultiThreadedDebugDLL, to avoid DLL dependencies on VCRUNTIME140d.dll and MSVCP140d.dll. -->
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
</ClCompile>
<Link>
<!-- Link statically against the runtime and STL, but link dynamically against the CRT by ignoring the static CRT
lib and instead linking against the Universal CRT DLL import library. This "hybrid" linking mechanism is
supported according to the CRT maintainer. Dynamic linking against the CRT makes the binaries a bit smaller
than they would otherwise be if the CRT, runtime, and STL were all statically linked in. -->
<IgnoreSpecificDefaultLibraries>%(IgnoreSpecificDefaultLibraries);libucrtd.lib</IgnoreSpecificDefaultLibraries>
<AdditionalOptions>%(AdditionalOptions) /defaultlib:ucrtd.lib</AdditionalOptions>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(HybridCrtConfiguration)'=='Release'">
<ClCompile>
<!-- We use MultiThreaded, rather than MultiThreadedDLL, to avoid DLL dependencies on VCRUNTIME140.dll and MSVCP140.dll. -->
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
</ClCompile>
<Link>
<!-- Link statically against the runtime and STL, but link dynamically against the CRT by ignoring the static CRT
lib and instead linking against the Universal CRT DLL import library. This "hybrid" linking mechanism is
supported according to the CRT maintainer. Dynamic linking against the CRT makes the binaries a bit smaller
than they would otherwise be if the CRT, runtime, and STL were all statically linked in. -->
<IgnoreSpecificDefaultLibraries>%(IgnoreSpecificDefaultLibraries);libucrt.lib</IgnoreSpecificDefaultLibraries>
<AdditionalOptions>%(AdditionalOptions) /defaultlib:ucrt.lib</AdditionalOptions>
</Link>
</ItemDefinitionGroup>
<!-- AppContainer/UWP projects must use MultiThreadedDLL -->
<ItemDefinitionGroup Condition="'$(AppContainerApplication)'=='true' and '$(_VC_Target_Library_Platform)'!='Desktop' And '$(Configuration)'=='Debug'">
<ClCompile>
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
</ClCompile>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(AppContainerApplication)'=='true' and '$(_VC_Target_Library_Platform)'!='Desktop' And '$(Configuration)'=='Release'">
<ClCompile>
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
</ClCompile>
</ItemDefinitionGroup>
<!-- Project configurations -->
<ItemGroup Label="ProjectConfigurations">
@@ -123,6 +73,7 @@
<ClCompile>
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<Optimization>Disabled</Optimization>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
</ClCompile>
<Link>
<GenerateDebugInformation>true</GenerateDebugInformation>
@@ -132,6 +83,7 @@
<ClCompile>
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<Optimization>MaxSpeed</Optimization>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
</ClCompile>

View File

@@ -51,10 +51,8 @@
<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" />
<PackageVersion Include="Microsoft.SemanticKernel.Connectors.Amazon" Version="1.66.0-alpha" />
<PackageVersion Include="Microsoft.SemanticKernel.Connectors.AzureAIInference" Version="1.66.0-beta" />
<PackageVersion Include="Microsoft.SemanticKernel.Connectors.Google" Version="1.66.0-alpha" />
<PackageVersion Include="Microsoft.SemanticKernel.Connectors.HuggingFace" Version="1.66.0-preview" />
<PackageVersion Include="Microsoft.SemanticKernel.Connectors.MistralAI" Version="1.66.0-alpha" />
<PackageVersion Include="Microsoft.SemanticKernel.Connectors.Ollama" Version="1.66.0-alpha" />
<PackageVersion Include="Microsoft.Toolkit.Uwp.Notifications" Version="7.1.2" />

View File

@@ -10,11 +10,11 @@
<h3 align="center">
<a href="#-installation">Installation</a>
<span> . </span>
<span> · </span>
<a href="https://aka.ms/powertoys-docs">Documentation</a>
<span> . </span>
<span> · </span>
<a href="https://aka.ms/powertoys-releaseblog">Blog</a>
<span> . </span>
<span> · </span>
<a href="#-whats-new">Release notes</a>
</h3>
<br/><br/>

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View File

@@ -110,6 +110,7 @@
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>_DEBUG;_WINDOWS;_USRDLL;CUSTOMACTIONTEST_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
</ClCompile>
<Link>
<GenerateDebugInformation>true</GenerateDebugInformation>
@@ -122,6 +123,7 @@
<Optimization>MaxSpeed</Optimization>
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>NDEBUG;_WINDOWS;_USRDLL;CUSTOMACTIONTEST_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<FunctionLevelLinking>true</FunctionLevelLinking>
</ClCompile>
<Link>

View File

@@ -1,5 +1,4 @@
<Project>
<Import Project="..\..\Directory.Build.props" Condition="Exists('..\..\Directory.Build.props')" />
<Import Project="..\..\src\Version.props" Condition="Exists('..\..\src\Version.props')" />
<PropertyGroup>
<!-- Set BaseIntermediateOutputPath for each project to avoid conflicts -->
@@ -9,4 +8,4 @@
<!-- Set MSBuildProjectExtensionsPath to use the BaseIntermediateOutputPath -->
<MSBuildProjectExtensionsPath Condition="'$(BaseIntermediateOutputPath)' != ''">$(BaseIntermediateOutputPath)</MSBuildProjectExtensionsPath>
</PropertyGroup>
</Project>
</Project>

View File

@@ -120,8 +120,8 @@
<Custom Action="SetUnApplyModulesRegistryChangeSetsParam" Before="UnApplyModulesRegistryChangeSets" />
<Custom Action="CheckGPO" After="InstallInitialize" Condition="NOT Installed" />
<Custom Action="SetBundleInstallLocationData" Before="SetBundleInstallLocation" Condition="NOT Installed" />
<Custom Action="SetBundleInstallLocation" After="InstallFiles" Condition="NOT Installed" />
<Custom Action="SetBundleInstallLocationData" Before="SetBundleInstallLocation" Condition="NOT Installed OR WIX_UPGRADE_DETECTED" />
<Custom Action="SetBundleInstallLocation" After="InstallFiles" Condition="NOT Installed OR WIX_UPGRADE_DETECTED" />
<Custom Action="ApplyModulesRegistryChangeSets" After="InstallFiles" Condition="NOT Installed" />
<Custom Action="InstallCmdPalPackage" After="InstallFiles" Condition="NOT Installed" />
<Custom Action="InstallPackageIdentityMSIX" After="InstallFiles" Condition="NOT Installed" />

View File

@@ -1,7 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|ARM64">
<Configuration>Debug</Configuration>
<Platform>ARM64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|ARM64">
<Configuration>Release</Configuration>
<Platform>ARM64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{F8B9F842-F5C3-4A2D-8C85-7F8B9E2B4F1D}</ProjectGuid>
<ConfigurationType>DynamicLibrary</ConfigurationType>
<CharacterSet>Unicode</CharacterSet>
<TargetName>SilentFilesInUseBAFunction</TargetName>
<ProjectName>PowerToysSetupCustomActionsVNext</ProjectName>
<ProjectModuleDefinitionFile>bafunctions.def</ProjectModuleDefinitionFile>
@@ -10,6 +33,7 @@
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<!-- Configuration-specific property groups -->
<PropertyGroup Condition="'$(Configuration)'=='Debug'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
@@ -41,10 +65,7 @@
</PropertyGroup>
<ItemGroup>
<ClCompile Include="SilentFilesInUseBAFunctions.cpp">
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>precomp.h</PrecompiledHeaderFile>
</ClCompile>
<ClCompile Include="SilentFilesInUseBAFunctions.cpp" />
<ClCompile Include="bafunctions.cpp">
<PrecompiledHeader>Create</PrecompiledHeader>
<PrecompiledHeaderFile>precomp.h</PrecompiledHeaderFile>
@@ -71,5 +92,31 @@
</Link>
</ItemDefinitionGroup>
<!-- C++ source compile-specific things for Debug/Release configurations -->
<ItemDefinitionGroup Condition="'$(Configuration)'=='Debug'">
<ClCompile>
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<Optimization>Disabled</Optimization>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
</ClCompile>
<Link>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
<ClCompile>
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<Optimization>MaxSpeed</Optimization>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
</ClCompile>
<Link>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
</Link>
</ItemDefinitionGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
</Project>

View File

@@ -18,6 +18,7 @@ public: // IBootstrapperApplication
BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "*** CUSTOM BA FUNCTION SYSTEM ACTIVE *** Running detect begin BA function. fCached=%d, registrationType=%d, cPackages=%u, fCancel=%d", fCached, registrationType, cPackages, *pfCancel);
LExit:
return hr;
}
@@ -31,6 +32,12 @@ public: // IBAFunctions
BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "*** CUSTOM BA FUNCTION SYSTEM ACTIVE *** Running plan begin BA function. cPackages=%u, fCancel=%d", cPackages, *pfCancel);
//-------------------------------------------------------------------------------------------------
// YOUR CODE GOES HERE
// BalExitOnFailure(hr, "Change this message to represent real error handling.");
//-------------------------------------------------------------------------------------------------
LExit:
return hr;
}
@@ -56,7 +63,6 @@ public: // IBAFunctions
)
{
HRESULT hr = S_OK;
UNREFERENCED_PARAMETER(source);
BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "*** CUSTOM BA FUNCTION CALLED *** Running OnExecuteFilesInUse BA function. packageId=%ls, cFiles=%u, recommendation=%d", wzPackageId, cFiles, nRecommendation);

View File

@@ -9,6 +9,12 @@
<RootNamespace>CalculatorEngineCommon</RootNamespace>
<AppxPackage>false</AppxPackage>
</PropertyGroup>
<!-- BEGIN common.build.pre.props -->
<PropertyGroup Label="Configuration">
<EnableHybridCRT>true</EnableHybridCRT>
<UseCrtSDKReferenceStaticWarning Condition="'$(EnableHybridCRT)'=='true'">false</UseCrtSDKReferenceStaticWarning>
</PropertyGroup>
<!-- END common.build.pre.props -->
<!-- BEGIN cppwinrt.build.pre.props -->
<PropertyGroup Label="Globals">
<CppWinRTEnabled>true</CppWinRTEnabled>
@@ -19,9 +25,11 @@
</PropertyGroup>
<PropertyGroup>
<MinimalCoreWin>true</MinimalCoreWin>
<AppContainerApplication>false</AppContainerApplication>
<AppContainerApplication>true</AppContainerApplication>
<WindowsStoreApp>true</WindowsStoreApp>
<ApplicationType>Windows Store</ApplicationType>
<UseCrtSDKReference Condition="'$(EnableHybridCRT)'=='true'">false</UseCrtSDKReference>
<!-- The SDK reference breaks the Hybrid CRT -->
</PropertyGroup>
<PropertyGroup>
<!-- We have to use the Desktop platform for Hybrid CRT to work. -->
@@ -140,5 +148,43 @@
</PropertyGroup>
<Error Condition="!Exists('..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets'))" />
</Target> <!-- END common.build.post.props -->
</Target>
<!-- BEGIN common.build.post.props -->
<!--
The Hybrid CRT model statically links the runtime and STL and dynamically
links the UCRT instead of the VC++ CRT. The UCRT ships with Windows.
WinAppSDK asserts that this is "supported according to the CRT maintainer."
This must come before Microsoft.Cpp.targets because it manipulates ClCompile.RuntimeLibrary.
-->
<ItemDefinitionGroup Condition="'$(EnableHybridCRT)'=='true' and '$(Configuration)'=='Debug'">
<ClCompile>
<!-- We use MultiThreadedDebug, rather than MultiThreadedDebugDLL, to avoid DLL dependencies on VCRUNTIME140d.dll and MSVCP140d.dll. -->
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
<LanguageStandard Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">stdcpp17</LanguageStandard>
</ClCompile>
<Link>
<!-- Link statically against the runtime and STL, but link dynamically against the CRT by ignoring the static CRT
lib and instead linking against the Universal CRT DLL import library. This "hybrid" linking mechanism is
supported according to the CRT maintainer. Dynamic linking against the CRT makes the binaries a bit smaller
than they would otherwise be if the CRT, runtime, and STL were all statically linked in. -->
<IgnoreSpecificDefaultLibraries>%(IgnoreSpecificDefaultLibraries);libucrtd.lib</IgnoreSpecificDefaultLibraries>
<AdditionalOptions>%(AdditionalOptions) /defaultlib:ucrtd.lib</AdditionalOptions>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(EnableHybridCRT)'=='true' and ('$(Configuration)'=='Release' or '$(Configuration)'=='AuditMode')">
<ClCompile>
<!-- We use MultiThreaded, rather than MultiThreadedDLL, to avoid DLL dependencies on VCRUNTIME140.dll and MSVCP140.dll. -->
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
</ClCompile>
<Link>
<!-- Link statically against the runtime and STL, but link dynamically against the CRT by ignoring the static CRT
lib and instead linking against the Universal CRT DLL import library. This "hybrid" linking mechanism is
supported according to the CRT maintainer. Dynamic linking against the CRT makes the binaries a bit smaller
than they would otherwise be if the CRT, runtime, and STL were all statically linked in. -->
<IgnoreSpecificDefaultLibraries>%(IgnoreSpecificDefaultLibraries);libucrt.lib</IgnoreSpecificDefaultLibraries>
<AdditionalOptions>%(AdditionalOptions) /defaultlib:ucrt.lib</AdditionalOptions>
</Link>
</ItemDefinitionGroup>
<!-- END common.build.post.props -->
</Project>

View File

@@ -216,10 +216,6 @@ namespace winrt::PowerToys::GPOWrapper::implementation
{
return static_cast<GpoRuleConfigured>(powertoys_gpo::getAllowedAdvancedPasteGoogleValue());
}
GpoRuleConfigured GPOWrapper::GetAllowedAdvancedPasteAnthropicValue()
{
return static_cast<GpoRuleConfigured>(powertoys_gpo::getAllowedAdvancedPasteAnthropicValue());
}
GpoRuleConfigured GPOWrapper::GetAllowedAdvancedPasteOllamaValue()
{
return static_cast<GpoRuleConfigured>(powertoys_gpo::getAllowedAdvancedPasteOllamaValue());

View File

@@ -60,7 +60,6 @@ namespace winrt::PowerToys::GPOWrapper::implementation
static GpoRuleConfigured GetAllowedAdvancedPasteAzureAIInferenceValue();
static GpoRuleConfigured GetAllowedAdvancedPasteMistralValue();
static GpoRuleConfigured GetAllowedAdvancedPasteGoogleValue();
static GpoRuleConfigured GetAllowedAdvancedPasteAnthropicValue();
static GpoRuleConfigured GetAllowedAdvancedPasteOllamaValue();
static GpoRuleConfigured GetAllowedAdvancedPasteFoundryLocalValue();
static GpoRuleConfigured GetConfiguredNewPlusEnabledValue();

View File

@@ -64,7 +64,6 @@ namespace PowerToys
static GpoRuleConfigured GetAllowedAdvancedPasteAzureAIInferenceValue();
static GpoRuleConfigured GetAllowedAdvancedPasteMistralValue();
static GpoRuleConfigured GetAllowedAdvancedPasteGoogleValue();
static GpoRuleConfigured GetAllowedAdvancedPasteAnthropicValue();
static GpoRuleConfigured GetAllowedAdvancedPasteOllamaValue();
static GpoRuleConfigured GetAllowedAdvancedPasteFoundryLocalValue();
static GpoRuleConfigured GetConfiguredNewPlusEnabledValue();

View File

@@ -63,12 +63,14 @@
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Debug'">
<ClCompile>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
<UseDebugLibraries>true</UseDebugLibraries>
<LinkIncremental>true</LinkIncremental>
</ClCompile>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
<ClCompile>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<UseDebugLibraries>false</UseDebugLibraries>
<WholeProgramOptimization>true</WholeProgramOptimization>
<LinkIncremental>false</LinkIncremental>

View File

@@ -46,6 +46,16 @@
<PropertyGroup>
<TargetName>notifications</TargetName>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Debug'">
<ClCompile>
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
</ClCompile>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
<ClCompile>
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
</ClCompile>
</ItemDefinitionGroup>
<ItemDefinitionGroup>
<ClCompile>
<PrecompiledHeaderOutputFile>$(IntDir)pch.pch</PrecompiledHeaderOutputFile>

View File

@@ -89,7 +89,6 @@ namespace powertoys_gpo
const std::wstring POLICY_ALLOW_ADVANCED_PASTE_AZURE_AI_INFERENCE = L"AllowAdvancedPasteAzureAIInference";
const std::wstring POLICY_ALLOW_ADVANCED_PASTE_MISTRAL = L"AllowAdvancedPasteMistral";
const std::wstring POLICY_ALLOW_ADVANCED_PASTE_GOOGLE = L"AllowAdvancedPasteGoogle";
const std::wstring POLICY_ALLOW_ADVANCED_PASTE_ANTHROPIC = L"AllowAdvancedPasteAnthropic";
const std::wstring POLICY_ALLOW_ADVANCED_PASTE_OLLAMA = L"AllowAdvancedPasteOllama";
const std::wstring POLICY_ALLOW_ADVANCED_PASTE_FOUNDRY_LOCAL = L"AllowAdvancedPasteFoundryLocal";
const std::wstring POLICY_MWB_CLIPBOARD_SHARING_ENABLED = L"MwbClipboardSharingEnabled";
@@ -615,11 +614,6 @@ namespace powertoys_gpo
return getConfiguredValue(POLICY_ALLOW_ADVANCED_PASTE_GOOGLE);
}
inline gpo_rule_configured_t getAllowedAdvancedPasteAnthropicValue()
{
return getConfiguredValue(POLICY_ALLOW_ADVANCED_PASTE_ANTHROPIC);
}
inline gpo_rule_configured_t getAllowedAdvancedPasteOllamaValue()
{
return getConfiguredValue(POLICY_ALLOW_ADVANCED_PASTE_OLLAMA);

View File

@@ -59,10 +59,8 @@
<PackageReference Include="CommunityToolkit.WinUI.Controls.Primitives" />
<!-- 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. -->
<PackageReference Include="MessagePack" />
<PackageReference Include="Microsoft.SemanticKernel.Connectors.Amazon" />
<PackageReference Include="Microsoft.SemanticKernel.Connectors.AzureAIInference" />
<PackageReference Include="Microsoft.SemanticKernel.Connectors.Google" />
<PackageReference Include="Microsoft.SemanticKernel.Connectors.HuggingFace" />
<PackageReference Include="Microsoft.SemanticKernel.Connectors.MistralAI" />
<PackageReference Include="Microsoft.SemanticKernel.Connectors.Ollama" />
<PackageReference Include="Microsoft.Extensions.Hosting" />

View File

@@ -112,11 +112,7 @@ namespace AdvancedPaste
/// Invoked when the application is launched.
/// </summary>
/// <param name="args">Details about the launch request and process.</param>
#if DEBUG
protected async override void OnLaunched(LaunchActivatedEventArgs args)
#else
protected override void OnLaunched(LaunchActivatedEventArgs args)
#endif
{
var cmdArgs = Environment.GetCommandLineArgs();
if (cmdArgs?.Length > 1)
@@ -138,10 +134,6 @@ namespace AdvancedPaste
{
ProcessNamedPipe(cmdArgs[2]);
}
#if DEBUG
await ShowWindow(); // This allows for direct access without using PowerToys Runner, not all functionality might work
#endif
}
private void ProcessNamedPipe(string pipeName)

View File

@@ -11,12 +11,6 @@ using AdvancedPaste.Settings;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.Connectors.Amazon;
using Microsoft.SemanticKernel.Connectors.AzureAIInference;
using Microsoft.SemanticKernel.Connectors.Google;
using Microsoft.SemanticKernel.Connectors.HuggingFace;
using Microsoft.SemanticKernel.Connectors.MistralAI;
using Microsoft.SemanticKernel.Connectors.Ollama;
using Microsoft.SemanticKernel.Connectors.OpenAI;
namespace AdvancedPaste.Services;
@@ -220,7 +214,7 @@ public sealed class AdvancedAIKernelService : KernelServiceBase
var serviceType = GetRuntimeConfiguration().ServiceType;
return new OpenAIPromptExecutionSettings
{
FunctionChoiceBehavior = FunctionChoiceBehavior.Required(),
FunctionChoiceBehavior = FunctionChoiceBehavior.Auto(),
Temperature = 0.01,
};
}

View File

@@ -181,8 +181,6 @@ namespace AdvancedPaste.Services.CustomActions
{
AIServiceType.Onnx => false,
AIServiceType.Ollama => false,
AIServiceType.Anthropic => false,
AIServiceType.AmazonBedrock => false,
_ => true,
};
}

View File

@@ -11,10 +11,8 @@ using AdvancedPaste.Models;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.Connectors.Amazon;
using Microsoft.SemanticKernel.Connectors.AzureAIInference;
using Microsoft.SemanticKernel.Connectors.Google;
using Microsoft.SemanticKernel.Connectors.HuggingFace;
using Microsoft.SemanticKernel.Connectors.MistralAI;
using Microsoft.SemanticKernel.Connectors.Ollama;
using Microsoft.SemanticKernel.Connectors.OpenAI;
@@ -29,11 +27,8 @@ namespace AdvancedPaste.Services.CustomActions
AIServiceType.AzureOpenAI,
AIServiceType.Mistral,
AIServiceType.Google,
AIServiceType.HuggingFace,
AIServiceType.AzureAIInference,
AIServiceType.Ollama,
AIServiceType.Anthropic,
AIServiceType.AmazonBedrock,
};
public static PasteAIProviderRegistration Registration { get; } = new(SupportedTypes, config => new SemanticKernelPasteProvider(config));
@@ -142,21 +137,12 @@ namespace AdvancedPaste.Services.CustomActions
case AIServiceType.Google:
kernelBuilder.AddGoogleAIGeminiChatCompletion(_config.Model, apiKey: apiKey);
break;
case AIServiceType.HuggingFace:
kernelBuilder.AddHuggingFaceChatCompletion(_config.Model, apiKey: apiKey);
break;
case AIServiceType.AzureAIInference:
kernelBuilder.AddAzureAIInferenceChatCompletion(_config.Model, apiKey: apiKey, endpoint: new Uri(endpoint));
break;
case AIServiceType.Ollama:
kernelBuilder.AddOllamaChatCompletion(_config.Model, endpoint: new Uri(endpoint));
break;
case AIServiceType.Anthropic:
kernelBuilder.AddBedrockChatCompletionService(_config.Model);
break;
case AIServiceType.AmazonBedrock:
kernelBuilder.AddBedrockChatCompletionService(_config.Model);
break;
default:
throw new NotSupportedException($"Provider '{_config.ProviderType}' is not supported by {nameof(SemanticKernelPasteProvider)}");
@@ -184,8 +170,6 @@ namespace AdvancedPaste.Services.CustomActions
return serviceType switch
{
AIServiceType.Ollama => false,
AIServiceType.Anthropic => false,
AIServiceType.AmazonBedrock => false,
_ => true,
};
}

View File

@@ -156,16 +156,10 @@ public sealed class EnhancedVaultCredentialsProvider : IAICredentialsProvider
resource = "https://ai.google.dev/";
serviceKey = "google";
break;
case AIServiceType.HuggingFace:
resource = "https://huggingface.co/settings/tokens";
serviceKey = "huggingface";
break;
case AIServiceType.FoundryLocal:
case AIServiceType.ML:
case AIServiceType.Onnx:
case AIServiceType.Ollama:
case AIServiceType.Anthropic:
case AIServiceType.AmazonBedrock:
return null;
default:
return null;

View File

@@ -15,7 +15,7 @@ namespace AdvancedPaste.Telemetry;
public class AdvancedPasteEndpointUsageEvent : EventBase, IEvent
{
/// <summary>
/// Gets or sets the AI provider type (e.g., OpenAI, AzureOpenAI, Anthropic).
/// Gets or sets the AI provider type (e.g., OpenAI, AzureOpenAI, Google).
/// </summary>
public string ProviderType { get; set; }

View File

@@ -798,7 +798,6 @@ namespace AdvancedPaste.ViewModels
AIServiceType.AzureAIInference => PowerToys.GPOWrapper.GPOWrapper.GetAllowedAdvancedPasteAzureAIInferenceValue() != PowerToys.GPOWrapper.GpoRuleConfigured.Disabled,
AIServiceType.Mistral => PowerToys.GPOWrapper.GPOWrapper.GetAllowedAdvancedPasteMistralValue() != PowerToys.GPOWrapper.GpoRuleConfigured.Disabled,
AIServiceType.Google => PowerToys.GPOWrapper.GPOWrapper.GetAllowedAdvancedPasteGoogleValue() != PowerToys.GPOWrapper.GpoRuleConfigured.Disabled,
AIServiceType.Anthropic => PowerToys.GPOWrapper.GPOWrapper.GetAllowedAdvancedPasteAnthropicValue() != PowerToys.GPOWrapper.GpoRuleConfigured.Disabled,
AIServiceType.Ollama => PowerToys.GPOWrapper.GPOWrapper.GetAllowedAdvancedPasteOllamaValue() != PowerToys.GPOWrapper.GpoRuleConfigured.Disabled,
AIServiceType.FoundryLocal => PowerToys.GPOWrapper.GPOWrapper.GetAllowedAdvancedPasteFoundryLocalValue() != PowerToys.GPOWrapper.GpoRuleConfigured.Disabled,
_ => true, // Allow unknown types by default

View File

@@ -82,6 +82,8 @@
<ClCompile>
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<RuntimeLibrary Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">MultiThreadedDebug</RuntimeLibrary>
<RuntimeLibrary Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">MultiThreadedDebug</RuntimeLibrary>
</ClCompile>
<Link>
<GenerateWindowsMetadata>false</GenerateWindowsMetadata>
@@ -93,6 +95,8 @@
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<RuntimeLibrary Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">MultiThreaded</RuntimeLibrary>
<RuntimeLibrary Condition="'$(Configuration)|$(Platform)'=='Release|x64'">MultiThreaded</RuntimeLibrary>
</ClCompile>
<Link>
<EnableCOMDATFolding>true</EnableCOMDATFolding>

View File

@@ -39,6 +39,7 @@
<ItemDefinitionGroup Condition="'$(Configuration)'=='Debug'">
<ClCompile>
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
<UseDebugLibraries>true</UseDebugLibraries>
<LinkIncremental>true</LinkIncremental>
</ClCompile>
@@ -48,6 +49,7 @@
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
<UseDebugLibraries>false</UseDebugLibraries>
<WholeProgramOptimization>true</WholeProgramOptimization>
<LinkIncremental>false</LinkIncremental>

View File

@@ -11,19 +11,17 @@
#include <logger/logger_settings.h>
#include <logger/logger.h>
#include <utils/logger_helper.h>
#include "LightSwitchStateManager.h"
#include <LightSwitchUtils.h>
SERVICE_STATUS g_ServiceStatus = {};
SERVICE_STATUS_HANDLE g_StatusHandle = nullptr;
HANDLE g_ServiceStopEvent = nullptr;
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);
DWORD WINAPI ServiceWorkerThread(LPVOID lpParam);
void ApplyTheme(bool shouldBeLight);
// Entry point for the executable
int _tmain(int argc, TCHAR* argv[])
@@ -124,31 +122,66 @@ VOID WINAPI ServiceCtrlHandler(DWORD dwCtrl)
}
}
static void update_sun_times(auto& settings)
void ApplyTheme(bool shouldBeLight)
{
double latitude = std::stod(settings.latitude);
double longitude = std::stod(settings.longitude);
const auto& s = LightSwitchSettings::settings();
if (s.changeSystem)
{
bool isSystemCurrentlyLight = GetCurrentSystemTheme();
if (shouldBeLight != isSystemCurrentlyLight)
{
SetSystemTheme(shouldBeLight);
Logger::info(L"[LightSwitchService] Changed system theme to {}.", shouldBeLight ? L"light" : L"dark");
}
}
if (s.changeApps)
{
bool isAppsCurrentlyLight = GetCurrentAppsTheme();
if (shouldBeLight != isAppsCurrentlyLight)
{
SetAppsTheme(shouldBeLight);
Logger::info(L"[LightSwitchService] Changed apps theme to {}.", shouldBeLight ? L"light" : L"dark");
}
}
}
static void DetectAndHandleExternalThemeChange(LightSwitchStateManager& stateManager)
{
const auto& s = LightSwitchSettings::settings();
if (s.scheduleMode == ScheduleMode::Off)
return;
SYSTEMTIME st;
GetLocalTime(&st);
int nowMinutes = st.wHour * 60 + st.wMinute;
SunTimes newTimes = CalculateSunriseSunset(latitude, longitude, st.wYear, st.wMonth, st.wDay);
// Compute effective boundaries (with offsets if needed)
int effectiveLight = s.lightTime;
int effectiveDark = s.darkTime;
int newLightTime = newTimes.sunriseHour * 60 + newTimes.sunriseMinute;
int newDarkTime = newTimes.sunsetHour * 60 + newTimes.sunsetMinute;
try
if (s.scheduleMode == ScheduleMode::SunsetToSunrise)
{
auto values = PowerToysSettings::PowerToyValues::load_from_settings_file(L"LightSwitch");
values.add_property(L"lightTime", newLightTime);
values.add_property(L"darkTime", newDarkTime);
values.save_to_settings_file();
Logger::info(L"[LightSwitchService] Updated sun times and saved to config.");
effectiveLight = (s.lightTime + s.sunrise_offset) % 1440;
effectiveDark = (s.darkTime + s.sunset_offset) % 1440;
}
catch (const std::exception& e)
// Use shared helper (handles wraparound logic)
bool shouldBeLight = ShouldBeLight(nowMinutes, effectiveLight, effectiveDark);
// Compare current system/apps theme
bool currentSystemLight = GetCurrentSystemTheme();
bool currentAppsLight = GetCurrentAppsTheme();
bool systemMismatch = s.changeSystem && (currentSystemLight != shouldBeLight);
bool appsMismatch = s.changeApps && (currentAppsLight != shouldBeLight);
// Trigger manual override only if mismatch and not already active
if ((systemMismatch || appsMismatch) && !stateManager.GetState().isManualOverride)
{
std::wstring wmsg(e.what(), e.what() + strlen(e.what()));
Logger::error(L"[LightSwitchService] Exception during sun time update: {}", wmsg);
Logger::info(L"[LightSwitchService] External theme change detected (Windows Settings). Entering manual override mode.");
stateManager.OnManualOverride();
}
}
@@ -162,307 +195,99 @@ DWORD WINAPI ServiceWorkerThread(LPVOID lpParam)
Logger::info(L"[LightSwitchService] Worker thread starting...");
Logger::info(L"[LightSwitchService] Parent PID: {}", parentPid);
// ────────────────────────────────────────────────────────────────
// Initialization
// ────────────────────────────────────────────────────────────────
static LightSwitchStateManager stateManager;
LightSwitchSettings::instance().InitFileWatcher();
HANDLE hManualOverride = OpenEventW(SYNCHRONIZE | EVENT_MODIFY_STATE, FALSE, L"POWERTOYS_LIGHTSWITCH_MANUAL_OVERRIDE");
HANDLE hSettingsChanged = LightSwitchSettings::instance().GetSettingsChangedEvent();
LightSwitchSettings::instance().LoadSettings();
auto& settings = LightSwitchSettings::instance().settings();
const auto& settings = LightSwitchSettings::instance().settings();
SYSTEMTIME st;
GetLocalTime(&st);
int nowMinutes = st.wHour * 60 + st.wMinute;
// Handle initial theme application if necessary
if (settings.scheduleMode != ScheduleMode::Off)
{
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 set to Off.");
}
Logger::info(L"[LightSwitchService] Initialized at {:02d}:{:02d}.", st.wHour, st.wMinute);
stateManager.SyncInitialThemeState();
stateManager.OnTick(nowMinutes);
g_lastUpdatedDay = st.wDay;
Logger::info(L"[LightSwitchService] Initializing g_lastUpdatedDay to {}.", g_lastUpdatedDay);
ULONGLONG lastSettingsReload = 0;
// ticker loop
// ────────────────────────────────────────────────────────────────
// Worker Loop
// ────────────────────────────────────────────────────────────────
for (;;)
{
HANDLE waits[2] = { g_ServiceStopEvent, hParent };
DWORD count = hParent ? 2 : 1;
bool skipRest = false;
const auto& settings = LightSwitchSettings::instance().settings();
// 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 is OFF - suspending scheduler but keeping service alive.");
if (!hManualOverride)
hManualOverride = OpenEventW(SYNCHRONIZE | EVENT_MODIFY_STATE, FALSE, L"POWERTOYS_LIGHTSWITCH_MANUAL_OVERRIDE");
HANDLE waitsOff[4];
DWORD countOff = 0;
waitsOff[countOff++] = g_ServiceStopEvent;
if (hParent)
waitsOff[countOff++] = hParent;
if (hManualOverride)
waitsOff[countOff++] = hManualOverride;
waitsOff[countOff++] = LightSwitchSettings::instance().GetSettingsChangedEvent();
for (;;)
{
DWORD wait = WaitForMultipleObjects(countOff, waitsOff, FALSE, INFINITE);
if (wait == WAIT_OBJECT_0)
{
Logger::info(L"[LightSwitchService] Stop event triggered - exiting worker loop.");
goto cleanup;
}
if (hParent && wait == WAIT_OBJECT_0 + 1)
{
Logger::info(L"[LightSwitchService] Parent exited - stopping service.");
goto cleanup;
}
if (wait == WAIT_OBJECT_0 + (hParent ? 2 : 1))
{
Logger::info(L"[LightSwitchService] Manual override received while schedule OFF.");
ResetEvent(hManualOverride);
continue;
}
if (wait == WAIT_OBJECT_0 + (hParent ? 3 : 2))
{
Logger::trace(L"[LightSwitchService] Settings change event triggered, reloading settings...");
ResetEvent(LightSwitchSettings::instance().GetSettingsChangedEvent());
const auto& newSettings = LightSwitchSettings::instance().settings();
lastSettingsReload = GetTickCount64();
if (newSettings.scheduleMode != ScheduleMode::Off)
{
Logger::info(L"[LightSwitchService] Schedule re-enabled, resuming normal loop.");
break;
}
}
}
continue;
}
bool scheduleJustEnabled = (prevMode == ScheduleMode::Off && settings.scheduleMode != ScheduleMode::Off);
prevMode = settings.scheduleMode;
ULONGLONG nowTick = GetTickCount64();
bool recentSettingsReload = (nowTick - lastSettingsReload < 2000);
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)
{
Logger::debug(L"[LightSwitchService] Manual override active = {}", manualOverrideActive);
lastOverrideStatus = manualOverrideActive;
}
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)
{
lightBoundary = (settings.lightTime + settings.sunrise_offset) % 1440;
darkBoundary = (settings.darkTime + settings.sunset_offset) % 1440;
}
else
{
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).");
}
HANDLE waits[4];
DWORD count = 0;
waits[count++] = g_ServiceStopEvent;
if (hParent)
waits[count++] = hParent;
if (hManualOverride)
manualOverrideActive = (WaitForSingleObject(hManualOverride, 0) == WAIT_OBJECT_0);
waits[count++] = hManualOverride;
waits[count++] = hSettingsChanged;
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 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)
{
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;
prevLon = settings.longitude;
}
SYSTEMTIME st;
GetLocalTime(&st);
int nowMinutes = st.wHour * 60 + st.wMinute;
if ((g_lastUpdatedDay != st.wDay) && (settings.scheduleMode == ScheduleMode::SunsetToSunrise))
{
update_sun_times(settings);
g_lastUpdatedDay = st.wDay;
prevMinutes = -1;
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=%s",
st.wHour,
st.wMinute,
currentSettings.lightTime / 60,
currentSettings.lightTime % 60,
currentSettings.darkTime / 60,
currentSettings.darkTime % 60,
ToString(currentSettings.scheduleMode).c_str());
Logger::info(msg);
LightSwitchSettings::instance().ApplyThemeIfNecessary();
}
// ─── Wait For Next Minute Tick Or Stop Event ────────────────────────────────
// Wait for one of these to trigger or for a new minute tick
SYSTEMTIME st;
GetLocalTime(&st);
int msToNextMinute = (60 - st.wSecond) * 1000 - st.wMilliseconds;
if (msToNextMinute < 50)
msToNextMinute = 50;
prevMinutes = nowMinutes;
DWORD wait = WaitForMultipleObjects(count, waits, FALSE, msToNextMinute);
if (wait == WAIT_TIMEOUT)
{
// regular minute tick
GetLocalTime(&st);
nowMinutes = st.wHour * 60 + st.wMinute;
DetectAndHandleExternalThemeChange(stateManager);
stateManager.OnTick(nowMinutes);
continue;
}
if (wait == WAIT_OBJECT_0)
{
Logger::info(L"[LightSwitchService] Stop event triggered - exiting worker loop.");
Logger::info(L"[LightSwitchService] Stop event triggered exiting.");
break;
}
if (hParent && wait == WAIT_OBJECT_0 + 1)
{
Logger::info(L"[LightSwitchService] Parent process exited - stopping service.");
Logger::info(L"[LightSwitchService] Parent process exited stopping service.");
break;
}
if (hManualOverride && wait == WAIT_OBJECT_0 + (hParent ? 2 : 1))
{
Logger::info(L"[LightSwitchService] Manual override event detected.");
stateManager.OnManualOverride();
ResetEvent(hManualOverride);
continue;
}
if (wait == WAIT_OBJECT_0 + (hParent ? (hManualOverride ? 3 : 2) : 2))
{
Logger::info(L"[LightSwitchService] Settings file changed event detected.");
ResetEvent(hSettingsChanged);
LightSwitchSettings::instance().LoadSettings();
stateManager.OnSettingsChanged();
continue;
}
}
cleanup:
// ────────────────────────────────────────────────────────────────
// Cleanup
// ────────────────────────────────────────────────────────────────
if (hManualOverride)
CloseHandle(hManualOverride);
if (hParent)
CloseHandle(hParent);
Logger::info(L"[LightSwitchService] Worker thread exiting cleanly.");
return 0;
}

View File

@@ -75,6 +75,7 @@
<ItemGroup>
<ClCompile Include="LightSwitchService.cpp" />
<ClCompile Include="LightSwitchSettings.cpp" />
<ClCompile Include="LightSwitchStateManager.cpp" />
<ClCompile Include="SettingsConstants.cpp" />
<ClCompile Include="ThemeHelper.cpp" />
<ClCompile Include="ThemeScheduler.cpp" />
@@ -85,6 +86,8 @@
</ItemGroup>
<ItemGroup>
<ClInclude Include="LightSwitchSettings.h" />
<ClInclude Include="LightSwitchStateManager.h" />
<ClInclude Include="LightSwitchUtils.h" />
<ClInclude Include="SettingsConstants.h" />
<ClInclude Include="SettingsObserver.h" />
<ClInclude Include="ThemeHelper.h" />

View File

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

View File

@@ -2,10 +2,8 @@
#include <common/utils/json.h>
#include <common/SettingsAPI/settings_helpers.h>
#include "SettingsObserver.h"
#include "ThemeHelper.h"
#include <filesystem>
#include <fstream>
#include <WinHookEventIDs.h>
#include <logger.h>
using namespace std;
@@ -69,7 +67,6 @@ void LightSwitchSettings::InitFileWatcher()
try
{
LoadSettings();
ApplyThemeIfNecessary();
SetEvent(m_settingsChangedEvent);
}
catch (const std::exception& e)
@@ -250,48 +247,3 @@ 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

@@ -81,7 +81,6 @@ public:
void RemoveObserver(SettingsObserver& observer);
void LoadSettings();
void ApplyThemeIfNecessary();
HANDLE GetSettingsChangedEvent() const;

View File

@@ -0,0 +1,243 @@
#include "pch.h"
#include "LightSwitchStateManager.h"
#include <logger.h>
#include <LightSwitchUtils.h>
#include "ThemeScheduler.h"
#include <ThemeHelper.h>
void ApplyTheme(bool shouldBeLight);
// Constructor
LightSwitchStateManager::LightSwitchStateManager()
{
Logger::info(L"[LightSwitchStateManager] Initialized");
}
// Called when settings.json changes
void LightSwitchStateManager::OnSettingsChanged()
{
std::lock_guard<std::mutex> lock(_stateMutex);
Logger::info(L"[LightSwitchStateManager] Settings changed event received");
// If manual override was active, clear it so new settings take effect
if (_state.isManualOverride)
{
Logger::info(L"[LightSwitchStateManager] Clearing manual override due to settings update.");
_state.isManualOverride = false;
}
EvaluateAndApplyIfNeeded();
}
// Called once per minute
void LightSwitchStateManager::OnTick(int currentMinutes)
{
std::lock_guard<std::mutex> lock(_stateMutex);
Logger::debug(L"[LightSwitchStateManager] Tick received: {}", currentMinutes);
EvaluateAndApplyIfNeeded();
}
// Called when manual override is triggered
void LightSwitchStateManager::OnManualOverride()
{
std::lock_guard<std::mutex> lock(_stateMutex);
Logger::info(L"[LightSwitchStateManager] Manual override triggered");
_state.isManualOverride = !_state.isManualOverride;
// When entering manual override, sync internal theme state to match the current system
if (_state.isManualOverride)
{
_state.isSystemLightActive = GetCurrentSystemTheme();
_state.isAppsLightActive = GetCurrentAppsTheme();
Logger::info(L"[LightSwitchStateManager] Synced internal theme state to current system theme ({}) and apps theme ({}).",
(_state.isSystemLightActive ? L"light" : L"dark"),
(_state.isAppsLightActive ? L"light" : L"dark"));
}
EvaluateAndApplyIfNeeded();
}
// Helpers
bool LightSwitchStateManager::CoordinatesAreValid(const std::wstring& lat, const std::wstring& lon)
{
try
{
double latVal = std::stod(lat);
double lonVal = std::stod(lon);
return !(latVal == 0 && lonVal == 0) && (latVal >= -90.0 && latVal <= 90.0) && (lonVal >= -180.0 && lonVal <= 180.0);
}
catch (...)
{
return false;
}
}
void LightSwitchStateManager::SyncInitialThemeState()
{
std::lock_guard<std::mutex> lock(_stateMutex);
_state.isSystemLightActive = GetCurrentSystemTheme();
_state.isAppsLightActive = GetCurrentAppsTheme();
Logger::info(L"[LightSwitchStateManager] Synced initial state to current system theme ({})",
_state.isSystemLightActive ? L"light" : L"dark");
Logger::info(L"[LightSwitchStateManager] Synced initial state to current apps theme ({})",
_state.isAppsLightActive ? L"light" : L"dark");
}
static std::pair<int, int> update_sun_times(auto& settings)
{
double latitude = std::stod(settings.latitude);
double longitude = std::stod(settings.longitude);
SYSTEMTIME st;
GetLocalTime(&st);
SunTimes newTimes = CalculateSunriseSunset(latitude, longitude, st.wYear, st.wMonth, st.wDay);
int newLightTime = newTimes.sunriseHour * 60 + newTimes.sunriseMinute;
int newDarkTime = newTimes.sunsetHour * 60 + newTimes.sunsetMinute;
try
{
auto values = PowerToysSettings::PowerToyValues::load_from_settings_file(L"LightSwitch");
values.add_property(L"lightTime", newLightTime);
values.add_property(L"darkTime", newDarkTime);
values.save_to_settings_file();
Logger::info(L"[LightSwitchService] Updated sun times and saved to config.");
}
catch (const std::exception& e)
{
std::string msg = e.what();
std::wstring wmsg(msg.begin(), msg.end());
Logger::error(L"[LightSwitchService] Exception during sun time update: {}", wmsg);
}
return { newLightTime, newDarkTime };
}
// Internal: decide what should happen now
void LightSwitchStateManager::EvaluateAndApplyIfNeeded()
{
LightSwitchSettings::instance().LoadSettings();
const auto& _currentSettings = LightSwitchSettings::settings();
auto now = GetNowMinutes();
// Early exit: OFF mode just pauses activity
if (_currentSettings.scheduleMode == ScheduleMode::Off)
{
Logger::debug(L"[LightSwitchStateManager] Mode is OFF — pausing service logic.");
_state.lastTickMinutes = now;
return;
}
bool coordsValid = CoordinatesAreValid(_currentSettings.latitude, _currentSettings.longitude);
// Handle Sun Mode recalculation
if (_currentSettings.scheduleMode == ScheduleMode::SunsetToSunrise && coordsValid)
{
SYSTEMTIME st;
GetLocalTime(&st);
bool newDay = (_state.lastEvaluatedDay != st.wDay);
bool modeChangedToSun = (_state.lastAppliedMode != ScheduleMode::SunsetToSunrise &&
_currentSettings.scheduleMode == ScheduleMode::SunsetToSunrise);
if (newDay || modeChangedToSun)
{
Logger::info(L"[LightSwitchStateManager] Recalculating sun times (mode/day change).");
auto [newLightTime, newDarkTime] = update_sun_times(_currentSettings);
_state.lastEvaluatedDay = st.wDay;
_state.effectiveLightMinutes = newLightTime + _currentSettings.sunrise_offset;
_state.effectiveDarkMinutes = newDarkTime + _currentSettings.sunset_offset;
}
else
{
_state.effectiveLightMinutes = _currentSettings.lightTime + _currentSettings.sunrise_offset;
_state.effectiveDarkMinutes = _currentSettings.darkTime + _currentSettings.sunset_offset;
}
}
else if (_currentSettings.scheduleMode == ScheduleMode::FixedHours)
{
_state.effectiveLightMinutes = _currentSettings.lightTime;
_state.effectiveDarkMinutes = _currentSettings.darkTime;
}
// Handle manual override logic
if (_state.isManualOverride)
{
bool crossedBoundary = false;
if (_state.lastTickMinutes != -1)
{
int prev = _state.lastTickMinutes;
// Handle midnight wraparound safely
if (now < prev)
{
crossedBoundary =
(prev <= _state.effectiveLightMinutes || now >= _state.effectiveLightMinutes) ||
(prev <= _state.effectiveDarkMinutes || now >= _state.effectiveDarkMinutes);
}
else
{
crossedBoundary =
(prev < _state.effectiveLightMinutes && now >= _state.effectiveLightMinutes) ||
(prev < _state.effectiveDarkMinutes && now >= _state.effectiveDarkMinutes);
}
}
if (crossedBoundary)
{
Logger::info(L"[LightSwitchStateManager] Manual override cleared after crossing boundary.");
_state.isManualOverride = false;
}
else
{
Logger::debug(L"[LightSwitchStateManager] Manual override active — skipping auto apply.");
_state.lastTickMinutes = now;
return;
}
}
_state.lastAppliedMode = _currentSettings.scheduleMode;
bool shouldBeLight = ShouldBeLight(now, _state.effectiveLightMinutes, _state.effectiveDarkMinutes);
bool appsNeedsToChange = _currentSettings.changeApps && (_state.isAppsLightActive != shouldBeLight);
bool systemNeedsToChange = _currentSettings.changeSystem && (_state.isSystemLightActive != shouldBeLight);
Logger::debug(
L"[LightSwitchStateManager] now = {:02d}:{:02d}, light boundary = {:02d}:{:02d} ({}), dark boundary = {:02d}:{:02d} ({})",
now / 60,
now % 60,
_state.effectiveLightMinutes / 60,
_state.effectiveLightMinutes % 60,
_state.effectiveLightMinutes,
_state.effectiveDarkMinutes / 60,
_state.effectiveDarkMinutes % 60,
_state.effectiveDarkMinutes);
Logger::debug("should be light = {}, apps needs change = {}, system needs change = {}",
shouldBeLight ? "true" : "false",
appsNeedsToChange ? "true" : "false",
systemNeedsToChange ? "true" : "false");
// Only apply theme if there's a change or no override active
if (!_state.isManualOverride && (appsNeedsToChange || systemNeedsToChange))
{
Logger::info(L"[LightSwitchStateManager] Applying {} theme", shouldBeLight ? L"light" : L"dark");
ApplyTheme(shouldBeLight);
_state.isSystemLightActive = GetCurrentSystemTheme();
_state.isAppsLightActive = GetCurrentAppsTheme();
Logger::debug(L"[LightSwitchStateManager] Synced post-apply theme state — System: {}, Apps: {}",
_state.isSystemLightActive ? L"light" : L"dark",
_state.isAppsLightActive ? L"light" : L"dark");
}
_state.lastTickMinutes = now;
}

View File

@@ -0,0 +1,47 @@
#pragma once
#include "LightSwitchSettings.h"
#include <optional>
// Represents runtime-only information (not saved in settings.json)
struct LightSwitchState
{
ScheduleMode lastAppliedMode = ScheduleMode::Off;
bool isManualOverride = false;
bool isSystemLightActive = false;
bool isAppsLightActive = false;
int lastEvaluatedDay = -1;
int lastTickMinutes = -1;
// Derived, runtime-resolved times
int effectiveLightMinutes = 0; // the boundary we actually act on
int effectiveDarkMinutes = 0; // includes offsets if needed
};
// The controller that reacts to settings changes, time ticks, and manual overrides.
class LightSwitchStateManager
{
public:
LightSwitchStateManager();
// Called when settings.json changes or stabilizes.
void OnSettingsChanged();
// Called every minute (from service worker tick).
void OnTick(int currentMinutes);
// Called when manual override is toggled (via shortcut or system change).
void OnManualOverride();
// Initial sync at startup to align internal state with system theme
void SyncInitialThemeState();
// Accessor for current state (optional, for debugging or telemetry)
const LightSwitchState& GetState() const { return _state; }
private:
LightSwitchState _state;
std::mutex _stateMutex;
void EvaluateAndApplyIfNeeded();
bool CoordinatesAreValid(const std::wstring& lat, const std::wstring& lon);
};

View File

@@ -0,0 +1,24 @@
#pragma once
#include <windows.h>
constexpr bool ShouldBeLight(int nowMinutes, int lightTime, int darkTime)
{
// Normalize values into [0, 1439]
int normalizedLightTime = (lightTime % 1440 + 1440) % 1440;
int normalizedDarkTime = (darkTime % 1440 + 1440) % 1440;
int normalizedNowMinutes = (nowMinutes % 1440 + 1440) % 1440;
// Case 1: Normal range, e.g. light mode comes before dark mode in the same day
if (normalizedLightTime < normalizedDarkTime)
return normalizedNowMinutes >= normalizedLightTime && normalizedNowMinutes < normalizedDarkTime;
// Case 2: Wrap-around range, e.g. light mode starts in the evening and dark mode starts in the morning
return normalizedNowMinutes >= normalizedLightTime || normalizedNowMinutes < normalizedDarkTime;
}
inline int GetNowMinutes()
{
SYSTEMTIME st;
GetLocalTime(&st);
return st.wHour * 60 + st.wMinute;
}

View File

@@ -152,16 +152,16 @@ namespace LightSwitch.UITests
var neededTabs = 6;
if (modeCombobox.Text != "Manual")
if (modeCombobox.Text != "Fixed hours")
{
modeCombobox.Click();
var manualListItem = testBase.Session.Find<Element>(By.AccessibilityId("ManualCBItem_LightSwitch"), 5000);
Assert.IsNotNull(manualListItem, "Manual combobox item not found.");
Assert.IsNotNull(manualListItem, "Fixed Hours combobox item not found.");
manualListItem.Click();
neededTabs = 1;
}
Assert.AreEqual("Manual", modeCombobox.Text, "Mode combobox should be set to Manual.");
Assert.AreEqual("Fixed hours", modeCombobox.Text, "Mode combobox should be set to Fixed hours.");
var timeline = testBase.Session.Find<Element>(By.AccessibilityId("Timeline_LightSwitch"), 5000);
Assert.IsNotNull(timeline, "Timeline not found.");
@@ -198,7 +198,7 @@ namespace LightSwitch.UITests
}
/// <summary>
/// Perform a update geolocation test operation
/// Perform a update manual location test operation
/// </summary>
public static void PerformUserSelectedLocationTest(UITestBase testBase)
{
@@ -216,19 +216,22 @@ namespace LightSwitch.UITests
Assert.AreEqual("Sunset to sunrise", modeCombobox.Text, "Mode combobox should be set to Sunset to sunrise.");
// Click the select location button
var setLocationButton = testBase.Session.Find<Element>(By.AccessibilityId("SetLocationButton_LightSwitch"), 5000);
Assert.IsNotNull(setLocationButton, "Set location button not found.");
setLocationButton.Click();
setLocationButton.Click(msPostAction: 1000);
var autoSuggestTextbox = testBase.Session.Find<Element>(By.AccessibilityId("CitySearchBox_LightSwitch"), 5000);
Assert.IsNotNull(autoSuggestTextbox, "City search box not found.");
autoSuggestTextbox.Click();
autoSuggestTextbox.SendKeys("Seattle");
autoSuggestTextbox.SendKeys(OpenQA.Selenium.Keys.Down);
autoSuggestTextbox.SendKeys(OpenQA.Selenium.Keys.Enter);
var latitudeBox = testBase.Session.Find<Element>(By.AccessibilityId("LatitudeBox_LightSwitch"), 5000);
Assert.IsNotNull(latitudeBox, "Latitude text box not found.");
latitudeBox.Click();
var latLong = testBase.Session.Find<Element>(By.AccessibilityId("LocationResultText_LightSwitch"), 5000);
Assert.IsFalse(string.IsNullOrWhiteSpace(latLong.Text));
testBase.Session.SendKeys(Key.Up);
var longitudeBox = testBase.Session.Find<Element>(By.AccessibilityId("LongitudeBox_LightSwitch"), 5000);
Assert.IsNotNull(longitudeBox, "Longitude text box not found.");
longitudeBox.Click();
testBase.Session.SendKeys(Key.Down);
var sunrise = testBase.Session.Find<Element>(By.AccessibilityId("SunriseText_LightSwitch"), 5000);
Assert.IsFalse(string.IsNullOrWhiteSpace(sunrise.Text));
@@ -256,13 +259,14 @@ namespace LightSwitch.UITests
Assert.AreEqual("Sunset to sunrise", modeCombobox.Text, "Mode combobox should be set to Sunset to sunrise.");
// Click the select city button
// Click the select location button
var setLocationButton = testBase.Session.Find<Element>(By.AccessibilityId("SetLocationButton_LightSwitch"), 5000);
Assert.IsNotNull(setLocationButton, "Set location button not found.");
setLocationButton.Click(msPostAction: 8000);
setLocationButton.Click(msPostAction: 1000);
var latLong = testBase.Session.Find<Element>(By.AccessibilityId("LocationResultText_LightSwitch"), 5000);
Assert.IsFalse(string.IsNullOrWhiteSpace(latLong.Text));
var syncLocationButton = testBase.Session.Find<Element>(By.AccessibilityId("SyncLocationButton_LightSwitch"), 5000);
Assert.IsNotNull(syncLocationButton, "Sync location button not found.");
syncLocationButton.Click(msPostAction: 8000);
var sunrise = testBase.Session.Find<Element>(By.AccessibilityId("SunriseText_LightSwitch"), 5000);
Assert.IsFalse(string.IsNullOrWhiteSpace(sunrise.Text));
@@ -363,6 +367,7 @@ namespace LightSwitch.UITests
var systemBeforeValue = GetSystemTheme();
var appsBeforeValue = GetAppsTheme();
Task.Delay(1000).Wait();
testBase.Session.SendKeys(activationKeys);
Task.Delay(5000).Wait();
@@ -389,6 +394,7 @@ namespace LightSwitch.UITests
var noneSystemBeforeValue = GetSystemTheme();
var noneAppsBeforeValue = GetAppsTheme();
Task.Delay(1000).Wait();
testBase.Session.SendKeys(activationKeys);
Task.Delay(5000).Wait();

View File

@@ -64,6 +64,7 @@
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
<LanguageStandard>stdcpplatest</LanguageStandard>
</ClCompile>
<Link>
@@ -81,6 +82,7 @@
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<LanguageStandard>stdcpplatest</LanguageStandard>
</ClCompile>
<Link>

View File

@@ -48,6 +48,7 @@
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
<LanguageStandard>stdcpplatest</LanguageStandard>
</ClCompile>
<Link>
@@ -65,6 +66,7 @@
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<LanguageStandard>stdcpplatest</LanguageStandard>
</ClCompile>
<Link>

View File

@@ -48,6 +48,7 @@
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
<LanguageStandard>stdcpplatest</LanguageStandard>
</ClCompile>
<Link>
@@ -65,6 +66,7 @@
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<LanguageStandard>stdcpplatest</LanguageStandard>
</ClCompile>
<Link>

View File

@@ -49,6 +49,7 @@
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
<LanguageStandard>stdcpplatest</LanguageStandard>
</ClCompile>
<Link>
@@ -66,6 +67,7 @@
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<LanguageStandard>stdcpplatest</LanguageStandard>
</ClCompile>
<Link>

View File

@@ -46,7 +46,7 @@
<PreprocessorDefinitions>_DEBUG;NEWPLUSSHELLEXTENSIONWIN10_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<PrecompiledHeader>Use</PrecompiledHeader>
<AdditionalIncludeDirectories>..\..\..\common\Telemetry;..\..\;..\..\..\;%(AdditionalIncludeDirectories);..\NewShellExtensionContextMenu;$(MSBuildThisFileDirectory)Generated Files</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>..\..\..\common\Telemetry;..\..\;..\..\..\;%(AdditionalIncludeDirectories);..\NewShellExtensionContextMenu;$(MSBuildThisFileDirectory)</AdditionalIncludeDirectories>
<CompileAsWinRT>false</CompileAsWinRT>
<LanguageStandard>stdcpplatest</LanguageStandard>
</ClCompile>
@@ -67,7 +67,7 @@
<PreprocessorDefinitions>NDEBUG;NEWPLUSSHELLEXTENSIONWIN10_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<PrecompiledHeader>Use</PrecompiledHeader>
<AdditionalIncludeDirectories>..\..\..\common\Telemetry;..\..\;..\..\..\;%(AdditionalIncludeDirectories);..\NewShellExtensionContextMenu;$(MSBuildThisFileDirectory)Generated Files</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>..\..\..\common\Telemetry;..\..\;..\..\..\;%(AdditionalIncludeDirectories);..\NewShellExtensionContextMenu;$(MSBuildThisFileDirectory)</AdditionalIncludeDirectories>
<CompileAsWinRT>false</CompileAsWinRT>
<LanguageStandard>stdcpplatest</LanguageStandard>
</ClCompile>

View File

@@ -67,6 +67,8 @@
<EnableUAC>false</EnableUAC>
<ModuleDefinitionFile>dll.def</ModuleDefinitionFile>
<AdditionalDependencies>runtimeobject.lib;$(CoreLibraryDependencies)</AdditionalDependencies>
<IgnoreSpecificDefaultLibraries>
</IgnoreSpecificDefaultLibraries>
</Link>
<PreBuildEvent>
<Command>del $(OutDir)\NewPlusPackage.msix /q
@@ -98,6 +100,8 @@ MakeAppx.exe pack /d . /p $(OutDir)NewPlusPackage.msix /nv</Command>
<EnableUAC>false</EnableUAC>
<ModuleDefinitionFile>dll.def</ModuleDefinitionFile>
<AdditionalDependencies>runtimeobject.lib;$(CoreLibraryDependencies)</AdditionalDependencies>
<IgnoreSpecificDefaultLibraries>
</IgnoreSpecificDefaultLibraries>
</Link>
<PreBuildEvent>
<Command>del $(OutDir)\NewPlusPackage.msix /q

View File

@@ -29,6 +29,7 @@
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<Optimization>Disabled</Optimization>
<SDLCheck>true</SDLCheck>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
</ClCompile>
<Link>
<GenerateDebugInformation>true</GenerateDebugInformation>
@@ -39,6 +40,7 @@
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<Optimization>MaxSpeed</Optimization>
<SDLCheck>false</SDLCheck>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
</ClCompile>

View File

@@ -29,6 +29,7 @@
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<Optimization>Disabled</Optimization>
<SDLCheck>true</SDLCheck>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
</ClCompile>
<Link>
<GenerateDebugInformation>true</GenerateDebugInformation>
@@ -39,6 +40,7 @@
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<Optimization>MaxSpeed</Optimization>
<SDLCheck>false</SDLCheck>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
</ClCompile>

View File

@@ -29,6 +29,7 @@
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<Optimization>Disabled</Optimization>
<SDLCheck>true</SDLCheck>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
</ClCompile>
<Link>
<GenerateDebugInformation>true</GenerateDebugInformation>
@@ -39,6 +40,7 @@
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<Optimization>MaxSpeed</Optimization>
<SDLCheck>false</SDLCheck>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
</ClCompile>

View File

@@ -0,0 +1,548 @@
//==============================================================================
//
// Zoomit
// Sysinternals - www.sysinternals.com
//
// GIF recording support using Windows Imaging Component (WIC)
//
//==============================================================================
#include "pch.h"
#include "GifRecordingSession.h"
#include "CaptureFrameWait.h"
#include <shcore.h>
extern DWORD g_RecordScaling;
namespace winrt
{
using namespace Windows::Foundation;
using namespace Windows::Graphics;
using namespace Windows::Graphics::Capture;
using namespace Windows::Graphics::DirectX;
using namespace Windows::Graphics::DirectX::Direct3D11;
using namespace Windows::Storage;
using namespace Windows::UI::Composition;
}
namespace util
{
using namespace robmikh::common::uwp;
}
const float CLEAR_COLOR[] = { 0.0f, 0.0f, 0.0f, 1.0f };
int32_t EnsureEvenGif(int32_t value)
{
if (value % 2 == 0)
{
return value;
}
else
{
return value + 1;
}
}
//----------------------------------------------------------------------------
//
// GifRecordingSession::GifRecordingSession
//
//----------------------------------------------------------------------------
GifRecordingSession::GifRecordingSession(
winrt::IDirect3DDevice const& device,
winrt::GraphicsCaptureItem const& item,
RECT const cropRect,
uint32_t frameRate,
winrt::Streams::IRandomAccessStream const& stream)
{
m_device = device;
m_d3dDevice = GetDXGIInterfaceFromObject<ID3D11Device>(m_device);
m_d3dDevice->GetImmediateContext(m_d3dContext.put());
m_item = item;
m_frameRate = frameRate;
m_stream = stream;
auto itemSize = item.Size();
auto inputWidth = EnsureEvenGif(itemSize.Width);
auto inputHeight = EnsureEvenGif(itemSize.Height);
m_frameWait = std::make_shared<CaptureFrameWait>(m_device, m_item, winrt::SizeInt32{ inputWidth, inputHeight });
auto weakPointer{ std::weak_ptr{ m_frameWait } };
m_itemClosed = item.Closed(winrt::auto_revoke, [weakPointer](auto&, auto&)
{
auto sharedPointer{ weakPointer.lock() };
if (sharedPointer)
{
sharedPointer->StopCapture();
}
});
// Get crop dimension
if ((cropRect.right - cropRect.left) != 0)
{
m_rcCrop = cropRect;
m_frameWait->ShowCaptureBorder(false);
}
else
{
m_rcCrop.left = 0;
m_rcCrop.top = 0;
m_rcCrop.right = inputWidth;
m_rcCrop.bottom = inputHeight;
}
// Apply scaling
constexpr int c_minimumSize = 34;
auto scaledWidth = MulDiv(m_rcCrop.right - m_rcCrop.left, g_RecordScaling, 100);
auto scaledHeight = MulDiv(m_rcCrop.bottom - m_rcCrop.top, g_RecordScaling, 100);
m_width = scaledWidth;
m_height = scaledHeight;
if (m_width < c_minimumSize)
{
m_width = c_minimumSize;
m_height = MulDiv(m_height, m_width, scaledWidth);
}
if (m_height < c_minimumSize)
{
m_height = c_minimumSize;
m_width = MulDiv(m_width, m_height, scaledHeight);
}
if (m_width > inputWidth)
{
m_width = inputWidth;
m_height = c_minimumSize, MulDiv(m_height, scaledWidth, m_width);
}
if (m_height > inputHeight)
{
m_height = inputHeight;
m_width = c_minimumSize, MulDiv(m_width, scaledHeight, m_height);
}
m_width = EnsureEvenGif(m_width);
m_height = EnsureEvenGif(m_height);
m_frameDelay = (frameRate > 0) ? (100 / frameRate) : 15;
// Initialize WIC
winrt::check_hresult(CoCreateInstance(
CLSID_WICImagingFactory,
nullptr,
CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(m_wicFactory.put())));
// Create WIC stream from IRandomAccessStream
winrt::check_hresult(m_wicFactory->CreateStream(m_wicStream.put()));
// Get the IStream from the IRandomAccessStream
winrt::com_ptr<IStream> streamInterop;
winrt::check_hresult(CreateStreamOverRandomAccessStream(
winrt::get_unknown(stream),
IID_PPV_ARGS(streamInterop.put())));
winrt::check_hresult(m_wicStream->InitializeFromIStream(streamInterop.get()));
// Create GIF encoder
winrt::check_hresult(m_wicFactory->CreateEncoder(
GUID_ContainerFormatGif,
nullptr,
m_gifEncoder.put()));
winrt::check_hresult(m_gifEncoder->Initialize(m_wicStream.get(), WICBitmapEncoderNoCache));
// Set global GIF metadata for looping (NETSCAPE2.0 application extension)
try
{
winrt::com_ptr<IWICMetadataQueryWriter> encoderMetadataWriter;
if (SUCCEEDED(m_gifEncoder->GetMetadataQueryWriter(encoderMetadataWriter.put())) && encoderMetadataWriter)
{
OutputDebugStringW(L"Setting NETSCAPE2.0 looping extension on encoder...\n");
// Set application extension
PROPVARIANT propValue;
PropVariantInit(&propValue);
propValue.vt = VT_UI1 | VT_VECTOR;
propValue.caub.cElems = 11;
propValue.caub.pElems = static_cast<UCHAR*>(CoTaskMemAlloc(11));
if (propValue.caub.pElems != nullptr)
{
memcpy(propValue.caub.pElems, "NETSCAPE2.0", 11);
HRESULT hr = encoderMetadataWriter->SetMetadataByName(L"/appext/application", &propValue);
if (SUCCEEDED(hr))
{
OutputDebugStringW(L"Encoder application extension set successfully\n");
}
else
{
OutputDebugStringW(L"Failed to set encoder application extension\n");
}
PropVariantClear(&propValue);
// Set loop count (0 = infinite)
PropVariantInit(&propValue);
propValue.vt = VT_UI1 | VT_VECTOR;
propValue.caub.cElems = 5;
propValue.caub.pElems = static_cast<UCHAR*>(CoTaskMemAlloc(5));
if (propValue.caub.pElems != nullptr)
{
propValue.caub.pElems[0] = 3;
propValue.caub.pElems[1] = 1;
propValue.caub.pElems[2] = 0;
propValue.caub.pElems[3] = 0;
propValue.caub.pElems[4] = 0;
hr = encoderMetadataWriter->SetMetadataByName(L"/appext/data", &propValue);
if (SUCCEEDED(hr))
{
OutputDebugStringW(L"Encoder loop count set successfully\n");
}
else
{
OutputDebugStringW(L"Failed to set encoder loop count\n");
}
PropVariantClear(&propValue);
}
}
}
else
{
OutputDebugStringW(L"Failed to get encoder metadata writer\n");
}
}
catch (...)
{
OutputDebugStringW(L"Warning: Failed to set GIF encoder looping metadata\n");
}
}
//----------------------------------------------------------------------------
//
// GifRecordingSession::~GifRecordingSession
//
//----------------------------------------------------------------------------
GifRecordingSession::~GifRecordingSession()
{
Close();
}
//----------------------------------------------------------------------------
//
// GifRecordingSession::Create
//
//----------------------------------------------------------------------------
std::shared_ptr<GifRecordingSession> GifRecordingSession::Create(
winrt::IDirect3DDevice const& device,
winrt::GraphicsCaptureItem const& item,
RECT const& crop,
uint32_t frameRate,
winrt::Streams::IRandomAccessStream const& stream)
{
return std::shared_ptr<GifRecordingSession>(new GifRecordingSession(device, item, crop, frameRate, stream));
}
//----------------------------------------------------------------------------
//
// GifRecordingSession::EncodeFrame
//
//----------------------------------------------------------------------------
HRESULT GifRecordingSession::EncodeFrame(ID3D11Texture2D* frameTexture)
{
try
{
// Create a staging texture for CPU access
D3D11_TEXTURE2D_DESC frameDesc;
frameTexture->GetDesc(&frameDesc);
// GIF encoding with palette generation is VERY slow at high resolutions (4K takes 1 second per frame!)
UINT targetWidth = frameDesc.Width;
UINT targetHeight = frameDesc.Height;
if (frameDesc.Width > static_cast<uint32_t>(m_width) || frameDesc.Height > static_cast<uint32_t>(m_height))
{
float scaleX = static_cast<float>(m_width) / frameDesc.Width;
float scaleY = static_cast<float>(m_height) / frameDesc.Height;
float scale = min(scaleX, scaleY);
targetWidth = static_cast<UINT>(frameDesc.Width * scale);
targetHeight = static_cast<UINT>(frameDesc.Height * scale);
// Ensure even dimensions for GIF
targetWidth = (targetWidth / 2) * 2;
targetHeight = (targetHeight / 2) * 2;
}
D3D11_TEXTURE2D_DESC stagingDesc = frameDesc;
stagingDesc.Usage = D3D11_USAGE_STAGING;
stagingDesc.BindFlags = 0;
stagingDesc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
stagingDesc.MiscFlags = 0;
winrt::com_ptr<ID3D11Texture2D> stagingTexture;
winrt::check_hresult(m_d3dDevice->CreateTexture2D(&stagingDesc, nullptr, stagingTexture.put()));
// Copy the frame to staging texture
m_d3dContext->CopyResource(stagingTexture.get(), frameTexture);
// Map the staging texture
D3D11_MAPPED_SUBRESOURCE mappedResource;
winrt::check_hresult(m_d3dContext->Map(stagingTexture.get(), 0, D3D11_MAP_READ, 0, &mappedResource));
// Create a new frame in the GIF
winrt::com_ptr<IWICBitmapFrameEncode> frameEncode;
winrt::com_ptr<IPropertyBag2> propertyBag;
winrt::check_hresult(m_gifEncoder->CreateNewFrame(frameEncode.put(), propertyBag.put()));
// Initialize the frame encoder with property bag
winrt::check_hresult(frameEncode->Initialize(propertyBag.get()));
// CRITICAL: For GIF, we MUST set size and pixel format BEFORE WriteSource
// Use target dimensions (may be downsampled)
winrt::check_hresult(frameEncode->SetSize(targetWidth, targetHeight));
// Set the pixel format to 8-bit indexed (required for GIF)
WICPixelFormatGUID pixelFormat = GUID_WICPixelFormat8bppIndexed;
winrt::check_hresult(frameEncode->SetPixelFormat(&pixelFormat));
// Create a WIC bitmap from the BGRA texture data
winrt::com_ptr<IWICBitmap> sourceBitmap;
winrt::check_hresult(m_wicFactory->CreateBitmapFromMemory(
frameDesc.Width,
frameDesc.Height,
GUID_WICPixelFormat32bppBGRA,
mappedResource.RowPitch,
frameDesc.Height * mappedResource.RowPitch,
static_cast<BYTE*>(mappedResource.pData),
sourceBitmap.put()));
// If we need downsampling, use WIC scaler
winrt::com_ptr<IWICBitmapSource> finalSource = sourceBitmap;
if (targetWidth != frameDesc.Width || targetHeight != frameDesc.Height)
{
winrt::com_ptr<IWICBitmapScaler> scaler;
winrt::check_hresult(m_wicFactory->CreateBitmapScaler(scaler.put()));
winrt::check_hresult(scaler->Initialize(
sourceBitmap.get(),
targetWidth,
targetHeight,
WICBitmapInterpolationModeHighQualityCubic));
finalSource = scaler;
OutputDebugStringW((L"Downsampled from " + std::to_wstring(frameDesc.Width) + L"x" + std::to_wstring(frameDesc.Height) +
L" to " + std::to_wstring(targetWidth) + L"x" + std::to_wstring(targetHeight) + L"\n").c_str());
}
// Use WriteSource - WIC will handle the BGRA to 8bpp indexed conversion
winrt::check_hresult(frameEncode->WriteSource(finalSource.get(), nullptr));
try
{
winrt::com_ptr<IWICMetadataQueryWriter> frameMetadataWriter;
if (SUCCEEDED(frameEncode->GetMetadataQueryWriter(frameMetadataWriter.put())) && frameMetadataWriter)
{
// Set the frame delay in the metadata (in hundredths of a second)
PROPVARIANT propValue;
PropVariantInit(&propValue);
propValue.vt = VT_UI2;
propValue.uiVal = static_cast<USHORT>(m_frameDelay);
frameMetadataWriter->SetMetadataByName(L"/grctlext/Delay", &propValue);
PropVariantClear(&propValue);
// Set disposal method (2 = restore to background, needed for animation)
PropVariantInit(&propValue);
propValue.vt = VT_UI1;
propValue.bVal = 2; // Disposal method: restore to background color
frameMetadataWriter->SetMetadataByName(L"/grctlext/Disposal", &propValue);
PropVariantClear(&propValue);
}
}
catch (...)
{
// Metadata setting failed, continue anyway
OutputDebugStringW(L"Warning: Failed to set GIF frame metadata\n");
}
// Commit the frame
OutputDebugStringW(L"About to commit frame to encoder...\n");
winrt::check_hresult(frameEncode->Commit());
OutputDebugStringW(L"Frame committed successfully\n");
// Unmap the staging texture
m_d3dContext->Unmap(stagingTexture.get(), 0);
// Increment and log frame count
m_frameCount++;
OutputDebugStringW((L"GIF Frame #" + std::to_wstring(m_frameCount) + L" fully encoded and committed\n").c_str());
return S_OK;
}
catch (const winrt::hresult_error& error)
{
OutputDebugStringW(error.message().c_str());
return error.code();
}
}
//----------------------------------------------------------------------------
//
// GifRecordingSession::StartAsync
//
//----------------------------------------------------------------------------
winrt::IAsyncAction GifRecordingSession::StartAsync()
{
auto expected = false;
if (m_isRecording.compare_exchange_strong(expected, true))
{
auto self = shared_from_this();
try
{
// Start capturing frames
auto frameStartTime = std::chrono::high_resolution_clock::now();
int captureAttempts = 0;
int successfulCaptures = 0;
int duplicatedFrames = 0;
// Keep track of the last frame to duplicate when needed
winrt::com_ptr<ID3D11Texture2D> lastCroppedTexture;
while (m_isRecording && !m_closed)
{
captureAttempts++;
auto frame = m_frameWait->TryGetNextFrame();
winrt::com_ptr<ID3D11Texture2D> croppedTexture;
if (frame)
{
successfulCaptures++;
auto contentSize = frame->ContentSize;
auto frameTexture = GetDXGIInterfaceFromObject<ID3D11Texture2D>(frame->FrameTexture);
D3D11_TEXTURE2D_DESC desc = {};
frameTexture->GetDesc(&desc);
// Use the smaller of the crop size or content size
auto width = min(m_rcCrop.right - m_rcCrop.left, contentSize.Width);
auto height = min(m_rcCrop.bottom - m_rcCrop.top, contentSize.Height);
D3D11_TEXTURE2D_DESC croppedDesc = {};
croppedDesc.Width = width;
croppedDesc.Height = height;
croppedDesc.MipLevels = 1;
croppedDesc.ArraySize = 1;
croppedDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
croppedDesc.SampleDesc.Count = 1;
croppedDesc.Usage = D3D11_USAGE_DEFAULT;
croppedDesc.BindFlags = D3D11_BIND_RENDER_TARGET;
winrt::check_hresult(m_d3dDevice->CreateTexture2D(&croppedDesc, nullptr, croppedTexture.put()));
// Set the content region to copy and clamp the coordinates
D3D11_BOX region = {};
region.left = std::clamp(m_rcCrop.left, static_cast<LONG>(0), static_cast<LONG>(desc.Width));
region.right = std::clamp(m_rcCrop.left + width, static_cast<LONG>(0), static_cast<LONG>(desc.Width));
region.top = std::clamp(m_rcCrop.top, static_cast<LONG>(0), static_cast<LONG>(desc.Height));
region.bottom = std::clamp(m_rcCrop.top + height, static_cast<LONG>(0), static_cast<LONG>(desc.Height));
region.back = 1;
// Copy the cropped region
m_d3dContext->CopySubresourceRegion(
croppedTexture.get(),
0,
0, 0, 0,
frameTexture.get(),
0,
&region);
// Save this as the last frame for duplication
lastCroppedTexture = croppedTexture;
}
else if (lastCroppedTexture)
{
// No new frame, duplicate the last one
duplicatedFrames++;
croppedTexture = lastCroppedTexture;
}
// Encode the frame (either new or duplicated)
if (croppedTexture)
{
HRESULT hr = EncodeFrame(croppedTexture.get());
if (FAILED(hr))
{
CloseInternal();
break;
}
}
// Wait for the next frame interval
co_await winrt::resume_after(std::chrono::milliseconds(1000 / m_frameRate));
}
// Commit the GIF encoder
if (m_gifEncoder)
{
auto frameEndTime = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(frameEndTime - frameStartTime).count();
OutputDebugStringW(L"Recording stopped. Committing GIF encoder...\n");
OutputDebugStringW((L"Total frames captured: " + std::to_wstring(m_frameCount) + L"\n").c_str());
OutputDebugStringW((L"Capture attempts: " + std::to_wstring(captureAttempts) + L"\n").c_str());
OutputDebugStringW((L"Successful captures: " + std::to_wstring(successfulCaptures) + L"\n").c_str());
OutputDebugStringW((L"Duplicated frames: " + std::to_wstring(duplicatedFrames) + L"\n").c_str());
OutputDebugStringW((L"Recording duration: " + std::to_wstring(duration) + L"ms\n").c_str());
OutputDebugStringW((L"Actual FPS: " + std::to_wstring(m_frameCount * 1000.0 / duration) + L"\n").c_str());
winrt::check_hresult(m_gifEncoder->Commit());
OutputDebugStringW(L"GIF encoder committed successfully\n");
}
}
catch (const winrt::hresult_error& error)
{
OutputDebugStringW(L"Error in GIF recording: ");
OutputDebugStringW(error.message().c_str());
OutputDebugStringW(L"\n");
// Try to commit the encoder even on error
if (m_gifEncoder)
{
try
{
m_gifEncoder->Commit();
}
catch (...) {}
}
CloseInternal();
}
}
co_return;
}
//----------------------------------------------------------------------------
//
// GifRecordingSession::Close
//
//----------------------------------------------------------------------------
void GifRecordingSession::Close()
{
auto expected = false;
if (m_closed.compare_exchange_strong(expected, true))
{
expected = true;
if (!m_isRecording.compare_exchange_strong(expected, false))
{
CloseInternal();
}
else
{
m_frameWait->StopCapture();
}
}
}
//----------------------------------------------------------------------------
//
// GifRecordingSession::CloseInternal
//
//----------------------------------------------------------------------------
void GifRecordingSession::CloseInternal()
{
m_frameWait->StopCapture();
m_itemClosed.revoke();
}

View File

@@ -0,0 +1,69 @@
//==============================================================================
//
// Zoomit
// Sysinternals - www.sysinternals.com
//
// GIF recording support using Windows Imaging Component (WIC)
//
//==============================================================================
#pragma once
#include "CaptureFrameWait.h"
#include <d3d11_4.h>
#include <vector>
class GifRecordingSession : public std::enable_shared_from_this<GifRecordingSession>
{
public:
[[nodiscard]] static std::shared_ptr<GifRecordingSession> Create(
winrt::Direct3D11::IDirect3DDevice const& device,
winrt::GraphicsCaptureItem const& item,
RECT const& cropRect,
uint32_t frameRate,
winrt::Streams::IRandomAccessStream const& stream);
~GifRecordingSession();
winrt::IAsyncAction StartAsync();
void EnableCursorCapture(bool enable = true) { m_frameWait->EnableCursorCapture(enable); }
void Close();
private:
GifRecordingSession(
winrt::Direct3D11::IDirect3DDevice const& device,
winrt::Capture::GraphicsCaptureItem const& item,
RECT const cropRect,
uint32_t frameRate,
winrt::Streams::IRandomAccessStream const& stream);
void CloseInternal();
HRESULT EncodeFrame(ID3D11Texture2D* texture);
private:
winrt::Direct3D11::IDirect3DDevice m_device{ nullptr };
winrt::com_ptr<ID3D11Device> m_d3dDevice;
winrt::com_ptr<ID3D11DeviceContext> m_d3dContext;
RECT m_rcCrop;
uint32_t m_frameRate;
winrt::GraphicsCaptureItem m_item{ nullptr };
winrt::GraphicsCaptureItem::Closed_revoker m_itemClosed;
std::shared_ptr<CaptureFrameWait> m_frameWait;
winrt::Streams::IRandomAccessStream m_stream{ nullptr };
// WIC components for GIF encoding
winrt::com_ptr<IWICImagingFactory> m_wicFactory;
winrt::com_ptr<IWICStream> m_wicStream;
winrt::com_ptr<IWICBitmapEncoder> m_gifEncoder;
winrt::com_ptr<IWICMetadataQueryWriter> m_encoderMetadataWriter;
std::atomic<bool> m_isRecording = false;
std::atomic<bool> m_closed = false;
uint32_t m_frameWidth=0;
uint32_t m_frameHeight=0;
uint32_t m_frameDelay=0;
uint32_t m_frameCount = 0;
int32_t m_width=0;
int32_t m_height=0;
};

View File

@@ -32,18 +32,18 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
// TEXTINCLUDE
//
1 TEXTINCLUDE
1 TEXTINCLUDE
BEGIN
"resource.h\0"
END
2 TEXTINCLUDE
2 TEXTINCLUDE
BEGIN
"#include ""winres.h""\r\n"
"\0"
END
3 TEXTINCLUDE
3 TEXTINCLUDE
BEGIN
"#include ""binres.rc""\0"
END
@@ -121,7 +121,7 @@ FONT 8, "MS Shell Dlg", 0, 0, 0x0
BEGIN
DEFPUSHBUTTON "OK",IDOK,166,306,50,14
PUSHBUTTON "Cancel",IDCANCEL,223,306,50,14
LTEXT "ZoomIt v9.10",IDC_VERSION,42,7,73,10
LTEXT "ZoomIt v9.20",IDC_VERSION,42,7,73,10
LTEXT "Copyright © 2006-2025 Mark Russinovich",IDC_COPYRIGHT,42,17,231,8
CONTROL "<a HREF=""https://www.sysinternals.com"">Sysinternals - www.sysinternals.com</a>",IDC_LINK,
"SysLink",WS_TABSTOP,42,26,150,9
@@ -272,13 +272,15 @@ BEGIN
LTEXT "Note: Recording is only available on Windows 10 (version 1903) and higher.",IDC_STATIC,7,77,246,19
LTEXT "Scaling:",IDC_STATIC,30,115,26,8
COMBOBOX IDC_RECORD_SCALING,61,114,26,30,CBS_DROPDOWNLIST | CBS_AUTOHSCROLL | CBS_OEMCONVERT | CBS_SORT | WS_VSCROLL | WS_TABSTOP
LTEXT "Format:",IDC_STATIC,30,132,26,8
COMBOBOX IDC_RECORD_FORMAT,61,131,60,30,CBS_DROPDOWNLIST | CBS_AUTOHSCROLL | CBS_OEMCONVERT | WS_VSCROLL | WS_TABSTOP
LTEXT "Frame Rate:",IDC_STATIC,119,115,44,8,NOT WS_VISIBLE
COMBOBOX IDC_RECORD_FRAME_RATE,166,114,42,30,CBS_DROPDOWNLIST | CBS_AUTOHSCROLL | CBS_OEMCONVERT | CBS_SORT | NOT WS_VISIBLE | WS_VSCROLL | WS_TABSTOP
LTEXT "To crop the portion of the screen that will be recorded, enter the hotkey with the Shift key in the opposite mode. ",IDC_STATIC,7,32,246,19
LTEXT "To record a specific window, enter the hotkey with the Alt key in the opposite mode.",IDC_STATIC,7,55,246,19
CONTROL "&Capture audio input:",IDC_CAPTURE_AUDIO,"Button",BS_AUTOCHECKBOX | BS_LEFTTEXT | WS_TABSTOP,7,137,83,10
COMBOBOX IDC_MICROPHONE,81,152,172,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
LTEXT "Microphone:",IDC_STATIC,32,154,47,8
CONTROL "&Capture audio input:",IDC_CAPTURE_AUDIO,"Button",BS_AUTOCHECKBOX | BS_LEFTTEXT | WS_TABSTOP,7,149,83,10
COMBOBOX IDC_MICROPHONE,81,164,172,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
LTEXT "Microphone:",IDC_STATIC,32,166,47,8
END
SNIP DIALOGEX 0, 0, 260, 68

View File

@@ -81,6 +81,7 @@
<Optimization>MaxSpeed</Optimization>
<PreprocessorDefinitions>__ZOOMIT_POWERTOYS__;_UNICODE;UNICODE;WINVER=0x602;NDEBUG;_WIN32_WINNT=0x602;_WIN32_WINDOWS=0x501;WIN32;_WINDOWS;MSVC6=1;_CRT_SECURE_NO_DEPRECATE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<StringPooling>true</StringPooling>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<FunctionLevelLinking>true</FunctionLevelLinking>
</ClCompile>
<ResourceCompile>
@@ -102,6 +103,7 @@
<Optimization>MaxSpeed</Optimization>
<PreprocessorDefinitions>__ZOOMIT_POWERTOYS__;_UNICODE;UNICODE;WINVER=0x602;NDEBUG;_WIN32_WINNT=0x602;_WIN32_WINDOWS=0x501;WIN32;_WINDOWS;MSVC6=1;_CRT_SECURE_NO_DEPRECATE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<StringPooling>true</StringPooling>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<FunctionLevelLinking>true</FunctionLevelLinking>
</ClCompile>
<ResourceCompile>
@@ -124,6 +126,7 @@
<Optimization>MaxSpeed</Optimization>
<PreprocessorDefinitions>__ZOOMIT_POWERTOYS__;_UNICODE;UNICODE;WINVER=0x602;NDEBUG;_WIN32_WINNT=0x602;_WIN32_WINDOWS=0x501;WIN32;_WINDOWS;MSVC6=1;_CRT_SECURE_NO_DEPRECATE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<StringPooling>true</StringPooling>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<FunctionLevelLinking>true</FunctionLevelLinking>
</ClCompile>
<ResourceCompile>
@@ -145,6 +148,7 @@
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>__ZOOMIT_POWERTOYS__;_UNICODE;UNICODE;WINVER=0x0602;_DEBUG;_WIN32_WINNT=0x602.MSVC6;_WIN32_WINDOWS=0x600;WIN32;_WINDOWS;_WIN32_WINNT=0x602;MSVC6=1;_CRT_SECURE_NO_DEPRECATE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
</ClCompile>
<ResourceCompile>
<PreprocessorDefinitions>_DEBUG;_M_IX86;%(PreprocessorDefinitions)</PreprocessorDefinitions>
@@ -165,6 +169,7 @@
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>__ZOOMIT_POWERTOYS__;_UNICODE;UNICODE;WINVER=0x0602;_DEBUG;_WIN32_WINNT=0x602.MSVC6;_WIN32_WINDOWS=0x600;WIN32;_WINDOWS;_WIN32_WINNT=0x602;MSVC6=1;_CRT_SECURE_NO_DEPRECATE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
</ClCompile>
<ResourceCompile>
<PreprocessorDefinitions>_DEBUG;_M_X64;%(PreprocessorDefinitions)</PreprocessorDefinitions>
@@ -186,6 +191,7 @@
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>__ZOOMIT_POWERTOYS__;_UNICODE;UNICODE;WINVER=0x0602;_DEBUG;_WIN32_WINNT=0x602.MSVC6;_WIN32_WINDOWS=0x600;WIN32;_WINDOWS;_WIN32_WINNT=0x602;MSVC6=1;_CRT_SECURE_NO_DEPRECATE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
</ClCompile>
<ResourceCompile>
<PreprocessorDefinitions>_DEBUG;_M_ARM64;%(PreprocessorDefinitions)</PreprocessorDefinitions>
@@ -234,6 +240,7 @@
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
</ClCompile>
<ClCompile Include="GifRecordingSession.cpp" />
<ClCompile Include="pch.cpp" />
<ClCompile Include="SelectRectangle.cpp">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Use</PrecompiledHeader>
@@ -288,6 +295,7 @@
<ClInclude Include="AudioSampleGenerator.h" />
<ClInclude Include="$(MSBuildThisFileDirectory)..\..\..\common\sysinternals\Eula\Eula.h" />
<ClInclude Include="$(MSBuildThisFileDirectory)..\ZoomItModuleInterface\Trace.h" />
<ClInclude Include="GifRecordingSession.h" />
<ClInclude Include="pch.h" />
<ClInclude Include="Registry.h" />
<ClInclude Include="resource.h" />

View File

@@ -54,6 +54,9 @@
<ClCompile Include="$(MSBuildThisFileDirectory)..\..\..\common\sysinternals\WindowsVersions.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="GifRecordingSession.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="Registry.h">
@@ -95,6 +98,9 @@
<ClInclude Include="ZoomItSettings.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="GifRecordingSession.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<Image Include="appicon.ico">

View File

@@ -3,6 +3,13 @@
#include "Registry.h"
#include "DemoType.h"
// Recording format enum
enum class RecordingFormat
{
GIF = 0,
MP4 = 1
};
DWORD g_ToggleKey = (HOTKEYF_CONTROL << 8)| '1';
DWORD g_LiveZoomToggleKey = ((HOTKEYF_CONTROL) << 8)| '4';
DWORD g_DrawToggleKey = ((HOTKEYF_CONTROL) << 8)| '2';
@@ -38,8 +45,10 @@ BOOLEAN g_DemoTypeUserDriven = false;
TCHAR g_DemoTypeFile[MAX_PATH] = {0};
DWORD g_DemoTypeSpeedSlider = static_cast<int>(((MIN_TYPING_SPEED - MAX_TYPING_SPEED) / 2) + MAX_TYPING_SPEED);
DWORD g_RecordFrameRate = 30;
// Divide by 100 to get actual scaling
DWORD g_RecordScaling = 100;
DWORD g_RecordScaling = 100;
DWORD g_RecordScalingGIF = 50;
DWORD g_RecordScalingMP4 = 100;
RecordingFormat g_RecordingFormat = RecordingFormat::GIF;
BOOLEAN g_CaptureAudio = FALSE;
TCHAR g_MicrophoneDeviceId[MAX_PATH] = {0};
@@ -79,7 +88,9 @@ REG_SETTING RegSettings[] = {
{ L"ZoominSliderLevel", SETTING_TYPE_DWORD, 0, &g_SliderZoomLevel, static_cast<DOUBLE>(g_SliderZoomLevel) },
{ L"Font", SETTING_TYPE_BINARY, sizeof g_LogFont, &g_LogFont, static_cast<DOUBLE>(0) },
{ L"RecordFrameRate", SETTING_TYPE_DWORD, 0, &g_RecordFrameRate, static_cast<DOUBLE>(g_RecordFrameRate) },
{ L"RecordScaling", SETTING_TYPE_DWORD, 0, &g_RecordScaling, static_cast<DOUBLE>(g_RecordScaling) },
{ L"RecordingFormat", SETTING_TYPE_DWORD, 0, &g_RecordingFormat, static_cast<DOUBLE>(0) },
{ L"RecordScalingGIF", SETTING_TYPE_DWORD, 0, &g_RecordScalingGIF, static_cast<DOUBLE>(g_RecordScalingGIF) },
{ L"RecordScalingMP4", SETTING_TYPE_DWORD, 0, &g_RecordScalingMP4, static_cast<DOUBLE>(g_RecordScalingMP4) },
{ L"CaptureAudio", SETTING_TYPE_BOOLEAN, 0, &g_CaptureAudio, static_cast<DOUBLE>(g_CaptureAudio) },
{ L"MicrophoneDeviceId", SETTING_TYPE_STRING, sizeof(g_MicrophoneDeviceId), g_MicrophoneDeviceId, static_cast<DOUBLE>(0) },
{ NULL, SETTING_TYPE_DWORD, 0, NULL, static_cast<DOUBLE>(0) }

File diff suppressed because it is too large Load Diff

View File

@@ -93,6 +93,7 @@
#define IDC_DEMOTYPE_SLIDER2 1074
#define IDC_DEMOTYPE_STATIC2 1074
#define IDC_COPYRIGHT 1075
#define IDC_RECORD_FORMAT 1076
#define IDC_PEN_WIDTH 1105
#define IDC_TIMER 1106
#define IDC_SMOOTH_IMAGE 1107

View File

@@ -14,6 +14,9 @@ namespace winrt::PowerToys::ZoomItSettingsInterop::implementation
const unsigned int SPECIAL_SEMANTICS_SHORTCUT = 1;
const unsigned int SPECIAL_SEMANTICS_COLOR = 2;
const unsigned int SPECIAL_SEMANTICS_LOG_FONT = 3;
const unsigned int SPECIAL_SEMANTICS_RECORDING_FORMAT = 4;
const unsigned int SPECIAL_SEMANTICS_RECORD_SCALING_GIF = 5;
const unsigned int SPECIAL_SEMANTICS_RECORD_SCALING_MP4 = 6;
std::vector<unsigned char> base64_decode(const std::wstring& base64_string)
{
@@ -72,6 +75,9 @@ namespace winrt::PowerToys::ZoomItSettingsInterop::implementation
{ L"PenColor", SPECIAL_SEMANTICS_COLOR },
{ L"BreakPenColor", SPECIAL_SEMANTICS_COLOR },
{ L"Font", SPECIAL_SEMANTICS_LOG_FONT },
{ L"RecordingFormat", SPECIAL_SEMANTICS_RECORDING_FORMAT },
{ L"RecordScalingGIF", SPECIAL_SEMANTICS_RECORD_SCALING_GIF },
{ L"RecordScalingMP4", SPECIAL_SEMANTICS_RECORD_SCALING_MP4 },
};
hstring ZoomItSettings::LoadSettingsJson()
@@ -103,6 +109,11 @@ namespace winrt::PowerToys::ZoomItSettingsInterop::implementation
value & 0xFF);
_settings.add_property(curSetting->ValueName, hotkey.get_json());
}
else if (special_semantics->second == SPECIAL_SEMANTICS_RECORDING_FORMAT)
{
std::wstring formatString = (value == 0) ? L"GIF" : L"MP4";
_settings.add_property(L"RecordFormat", formatString);
}
else if (special_semantics->second == SPECIAL_SEMANTICS_COLOR)
{
/* PowerToys settings likes colors as #FFFFFF strings.
@@ -156,6 +167,9 @@ namespace winrt::PowerToys::ZoomItSettingsInterop::implementation
curSetting++;
}
DWORD recordScaling = (g_RecordingFormat == static_cast<RecordingFormat>(0)) ? g_RecordScalingGIF : g_RecordScalingMP4;
_settings.add_property<DWORD>(L"RecordScaling", recordScaling);
return _settings.get_raw_json().Stringify();
}
@@ -167,6 +181,8 @@ namespace winrt::PowerToys::ZoomItSettingsInterop::implementation
PowerToysSettings::PowerToyValues valuesFromSettings =
PowerToysSettings::PowerToyValues::from_json_string(json, L"ZoomIt");
bool formatChanged = false;
PREG_SETTING curSetting = RegSettings;
while (curSetting->ValueName)
{
@@ -212,6 +228,42 @@ namespace winrt::PowerToys::ZoomItSettingsInterop::implementation
*static_cast<PDWORD>(curSetting->Setting) = value;
}
}
else if (special_semantics->second == SPECIAL_SEMANTICS_RECORDING_FORMAT)
{
// Convert string ("GIF" or "MP4") to DWORD enum value (0=GIF, 1=MP4)
auto possibleValue = valuesFromSettings.get_string_value(L"RecordFormat");
if (possibleValue.has_value())
{
RecordingFormat oldFormat = g_RecordingFormat;
DWORD formatValue = (possibleValue.value() == L"GIF") ? 0 : 1;
RecordingFormat newFormat = static_cast<RecordingFormat>(formatValue);
*static_cast<PDWORD>(curSetting->Setting) = formatValue;
if (oldFormat != newFormat)
{
formatChanged = true;
if (oldFormat == static_cast<RecordingFormat>(0))
{
g_RecordScalingGIF = g_RecordScaling;
}
else
{
g_RecordScalingMP4 = g_RecordScaling;
}
if (newFormat == static_cast<RecordingFormat>(0))
{
g_RecordScaling = g_RecordScalingGIF;
}
else
{
g_RecordScaling = g_RecordScalingMP4;
}
}
}
}
else if (special_semantics->second == SPECIAL_SEMANTICS_COLOR)
{
/* PowerToys settings likes colors as #FFFFFF strings.
@@ -275,6 +327,22 @@ namespace winrt::PowerToys::ZoomItSettingsInterop::implementation
}
curSetting++;
}
auto recordScalingValue = valuesFromSettings.get_uint_value(L"RecordScaling");
if (recordScalingValue.has_value() && !formatChanged)
{
g_RecordScaling = recordScalingValue.value();
if (g_RecordingFormat == static_cast<RecordingFormat>(0))
{
g_RecordScalingGIF = recordScalingValue.value();
}
else
{
g_RecordScalingMP4 = recordScalingValue.value();
}
}
reg.WriteRegSettings(RegSettings);
}
}

View File

@@ -29,6 +29,7 @@
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<Optimization>Disabled</Optimization>
<SDLCheck>true</SDLCheck>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
</ClCompile>
<Link>
<GenerateDebugInformation>true</GenerateDebugInformation>
@@ -39,6 +40,7 @@
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<Optimization>MaxSpeed</Optimization>
<SDLCheck>false</SDLCheck>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
</ClCompile>

View File

@@ -141,4 +141,43 @@
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets'))" />
</Target>
<!-- BEGIN common.build.post.props -->
<!--
The Hybrid CRT model statically links the runtime and STL and dynamically
links the UCRT instead of the VC++ CRT. The UCRT ships with Windows.
WinAppSDK asserts that this is "supported according to the CRT maintainer."
This must come before Microsoft.Cpp.targets because it manipulates ClCompile.RuntimeLibrary.
-->
<ItemDefinitionGroup Condition="'$(EnableHybridCRT)'=='true' and '$(Configuration)'=='Debug'">
<ClCompile>
<!-- We use MultiThreadedDebug, rather than MultiThreadedDebugDLL, to avoid DLL dependencies on VCRUNTIME140d.dll and MSVCP140d.dll. -->
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
</ClCompile>
<Link>
<!-- Link statically against the runtime and STL, but link dynamically against the CRT by ignoring the static CRT
lib and instead linking against the Universal CRT DLL import library. This "hybrid" linking mechanism is
supported according to the CRT maintainer. Dynamic linking against the CRT makes the binaries a bit smaller
than they would otherwise be if the CRT, runtime, and STL were all statically linked in. -->
<IgnoreSpecificDefaultLibraries>%(IgnoreSpecificDefaultLibraries);libucrtd.lib</IgnoreSpecificDefaultLibraries>
<AdditionalOptions>%(AdditionalOptions) /defaultlib:ucrtd.lib</AdditionalOptions>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(EnableHybridCRT)'=='true' and ('$(Configuration)'=='Release' or '$(Configuration)'=='AuditMode')">
<ClCompile>
<!-- We use MultiThreaded, rather than MultiThreadedDLL, to avoid DLL dependencies on VCRUNTIME140.dll and MSVCP140.dll. -->
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
</ClCompile>
<Link>
<!-- Link statically against the runtime and STL, but link dynamically against the CRT by ignoring the static CRT
lib and instead linking against the Universal CRT DLL import library. This "hybrid" linking mechanism is
supported according to the CRT maintainer. Dynamic linking against the CRT makes the binaries a bit smaller
than they would otherwise be if the CRT, runtime, and STL were all statically linked in. -->
<IgnoreSpecificDefaultLibraries>%(IgnoreSpecificDefaultLibraries);libucrt.lib</IgnoreSpecificDefaultLibraries>
<AdditionalOptions>%(AdditionalOptions) /defaultlib:ucrt.lib</AdditionalOptions>
</Link>
</ItemDefinitionGroup>
<!-- END common.build.post.props -->
</Project>

View File

@@ -68,7 +68,6 @@ public sealed partial class MainWindow : WindowEx,
public MainWindow()
{
InitializeComponent();
HideWindow();
_hwnd = new HWND(WinRT.Interop.WindowNative.GetWindowHandle(this).ToInt32());

View File

@@ -74,11 +74,35 @@
<ClCompile>
<LanguageStandard>stdcpp20</LanguageStandard>
</ClCompile>
<ClCompile>
<!-- We use MultiThreadedDebug, rather than MultiThreadedDebugDLL, to avoid DLL dependencies on VCRUNTIME140d.dll and MSVCP140d.dll. -->
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
</ClCompile>
<Link>
<!-- Link statically against the runtime and STL, but link dynamically against the CRT by ignoring the static CRT
lib and instead linking against the Universal CRT DLL import library. This "hybrid" linking mechanism is
supported according to the CRT maintainer. Dynamic linking against the CRT makes the binaries a bit smaller
than they would otherwise be if the CRT, runtime, and STL were all statically linked in. -->
<IgnoreSpecificDefaultLibraries>%(IgnoreSpecificDefaultLibraries);libucrtd.lib</IgnoreSpecificDefaultLibraries>
<AdditionalOptions>%(AdditionalOptions) /defaultlib:ucrtd.lib /profile /opt:ref /opt:icf</AdditionalOptions>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
<ClCompile>
<LanguageStandard>stdcpp20</LanguageStandard>
</ClCompile>
<ClCompile>
<!-- We use MultiThreaded, rather than MultiThreadedDLL, to avoid DLL dependencies on VCRUNTIME140.dll and MSVCP140.dll. -->
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
</ClCompile>
<Link>
<!-- Link statically against the runtime and STL, but link dynamically against the CRT by ignoring the static CRT
lib and instead linking against the Universal CRT DLL import library. This "hybrid" linking mechanism is
supported according to the CRT maintainer. Dynamic linking against the CRT makes the binaries a bit smaller
than they would otherwise be if the CRT, runtime, and STL were all statically linked in. -->
<IgnoreSpecificDefaultLibraries>%(IgnoreSpecificDefaultLibraries);libucrt.lib</IgnoreSpecificDefaultLibraries>
<AdditionalOptions>%(AdditionalOptions) /defaultlib:ucrt.lib /profile /opt:ref /opt:icf</AdditionalOptions>
</Link>
</ItemDefinitionGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">

View File

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

View File

@@ -15,8 +15,6 @@ namespace Microsoft.CmdPal.Ext.Apps.Programs;
public sealed partial class AppListItem : ListItem
{
private static readonly Tag _appTag = new("App");
private readonly AppCommand _appCommand;
private readonly AppItem _app;
private readonly Lazy<Details> _details;
@@ -48,7 +46,6 @@ public sealed partial class AppListItem : ListItem
_app = app;
Title = app.Name;
Subtitle = app.Subtitle;
Tags = [_appTag];
Icon = Icons.GenericAppIcon;
MoreCommands = AddPinCommands(_app.Commands!, isPinned);

View File

@@ -68,6 +68,34 @@
<WholeProgramOptimization>true</WholeProgramOptimization>
<LinkIncremental>false</LinkIncremental>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Debug'">
<ClCompile>
<!-- We use MultiThreadedDebug, rather than MultiThreadedDebugDLL, to avoid DLL dependencies on VCRUNTIME140d.dll and MSVCP140d.dll. -->
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
</ClCompile>
<Link>
<!-- Link statically against the runtime and STL, but link dynamically against the CRT by ignoring the static CRT
lib and instead linking against the Universal CRT DLL import library. This "hybrid" linking mechanism is
supported according to the CRT maintainer. Dynamic linking against the CRT makes the binaries a bit smaller
than they would otherwise be if the CRT, runtime, and STL were all statically linked in. -->
<IgnoreSpecificDefaultLibraries>%(IgnoreSpecificDefaultLibraries);libucrtd.lib</IgnoreSpecificDefaultLibraries>
<AdditionalOptions>%(AdditionalOptions) /defaultlib:ucrtd.lib /profile /opt:ref /opt:icf</AdditionalOptions>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
<ClCompile>
<!-- We use MultiThreaded, rather than MultiThreadedDLL, to avoid DLL dependencies on VCRUNTIME140.dll and MSVCP140.dll. -->
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
</ClCompile>
<Link>
<!-- Link statically against the runtime and STL, but link dynamically against the CRT by ignoring the static CRT
lib and instead linking against the Universal CRT DLL import library. This "hybrid" linking mechanism is
supported according to the CRT maintainer. Dynamic linking against the CRT makes the binaries a bit smaller
than they would otherwise be if the CRT, runtime, and STL were all statically linked in. -->
<IgnoreSpecificDefaultLibraries>%(IgnoreSpecificDefaultLibraries);libucrt.lib</IgnoreSpecificDefaultLibraries>
<AdditionalOptions>%(AdditionalOptions) /defaultlib:ucrt.lib /profile /opt:ref /opt:icf</AdditionalOptions>
</Link>
</ItemDefinitionGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>

View File

@@ -26,6 +26,7 @@
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<Optimization>Disabled</Optimization>
<SDLCheck>true</SDLCheck>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
</ClCompile>
<Link>
<GenerateDebugInformation>true</GenerateDebugInformation>
@@ -36,6 +37,7 @@
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<Optimization>MaxSpeed</Optimization>
<SDLCheck>false</SDLCheck>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
</ClCompile>

View File

@@ -30,6 +30,7 @@
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<Optimization>Disabled</Optimization>
<SDLCheck>true</SDLCheck>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
</ClCompile>
<Link>
<GenerateDebugInformation>true</GenerateDebugInformation>
@@ -40,6 +41,7 @@
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<Optimization>MaxSpeed</Optimization>
<SDLCheck>false</SDLCheck>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
</ClCompile>

View File

@@ -44,6 +44,7 @@
<ConformanceMode>true</ConformanceMode>
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<AdditionalOptions>/fsanitize=address /fsanitize-coverage=inline-8bit-counters /fsanitize-coverage=edge /fsanitize-coverage=trace-cmp /fsanitize-coverage=trace-div %(AdditionalOptions)</AdditionalOptions>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<LanguageStandard>stdcpplatest</LanguageStandard>
<AdditionalIncludeDirectories>..\;..\lib\;..\..\..\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
@@ -71,6 +72,7 @@
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<AdditionalDependencies>windowscodecs.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemGroup>

View File

@@ -28,6 +28,7 @@
<PlatformToolset>v143</PlatformToolset>
<WindowsPackageType>None</WindowsPackageType>
<WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>
<WindowsAppSdkUndockedRegFreeWinRTInitialize>true</WindowsAppSdkUndockedRegFreeWinRTInitialize>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">

View File

@@ -17,10 +17,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
FoundryLocal,
Mistral,
Google,
HuggingFace,
AzureAIInference,
Ollama,
Anthropic,
AmazonBedrock,
}
}

View File

@@ -29,11 +29,8 @@ namespace Microsoft.PowerToys.Settings.UI.Library
"ml" or "windowsml" or "winml" => AIServiceType.ML,
"mistral" => AIServiceType.Mistral,
"google" or "googleai" or "googlegemini" => AIServiceType.Google,
"huggingface" => AIServiceType.HuggingFace,
"azureaiinference" or "azureinference" => AIServiceType.AzureAIInference,
"ollama" => AIServiceType.Ollama,
"anthropic" => AIServiceType.Anthropic,
"amazonbedrock" or "bedrock" => AIServiceType.AmazonBedrock,
_ => AIServiceType.Unknown,
};
}
@@ -52,11 +49,8 @@ namespace Microsoft.PowerToys.Settings.UI.Library
AIServiceType.ML => "ML",
AIServiceType.Mistral => "Mistral",
AIServiceType.Google => "Google",
AIServiceType.HuggingFace => "HuggingFace",
AIServiceType.AzureAIInference => "AzureAIInference",
AIServiceType.Ollama => "Ollama",
AIServiceType.Anthropic => "Anthropic",
AIServiceType.AmazonBedrock => "AmazonBedrock",
AIServiceType.Unknown => string.Empty,
_ => throw new ArgumentOutOfRangeException(nameof(serviceType), serviceType, "Unsupported AI service type."),
};
@@ -76,11 +70,8 @@ namespace Microsoft.PowerToys.Settings.UI.Library
AIServiceType.ML => "ml",
AIServiceType.Mistral => "mistral",
AIServiceType.Google => "google",
AIServiceType.HuggingFace => "huggingface",
AIServiceType.AzureAIInference => "azureaiinference",
AIServiceType.Ollama => "ollama",
AIServiceType.Anthropic => "anthropic",
AIServiceType.AmazonBedrock => "amazonbedrock",
_ => string.Empty,
};
}

View File

@@ -15,31 +15,6 @@ public static class AIServiceTypeRegistry
{
private static readonly Dictionary<AIServiceType, AIServiceTypeMetadata> MetadataMap = new()
{
[AIServiceType.AmazonBedrock] = new AIServiceTypeMetadata
{
ServiceType = AIServiceType.AmazonBedrock,
DisplayName = "Amazon Bedrock",
IsAvailableInUI = false, // Currently disabled in UI
IconPath = "ms-appx:///Assets/Settings/Icons/Models/Bedrock.svg",
IsOnlineService = true,
LegalDescription = "AdvancedPaste_AmazonBedrock_LegalDescription",
TermsLabel = "AdvancedPaste_AmazonBedrock_TermsLabel",
TermsUri = new Uri("https://aws.amazon.com/service-terms/"),
PrivacyLabel = "AdvancedPaste_AmazonBedrock_PrivacyLabel",
PrivacyUri = new Uri("https://aws.amazon.com/privacy/"),
},
[AIServiceType.Anthropic] = new AIServiceTypeMetadata
{
ServiceType = AIServiceType.Anthropic,
DisplayName = "Anthropic",
IconPath = "ms-appx:///Assets/Settings/Icons/Models/Anthropic.svg",
IsOnlineService = true,
LegalDescription = "AdvancedPaste_Anthropic_LegalDescription",
TermsLabel = "AdvancedPaste_Anthropic_TermsLabel",
TermsUri = new Uri("https://privacy.claude.com/en/collections/10672567-policies-terms-of-service"),
PrivacyLabel = "AdvancedPaste_Anthropic_PrivacyLabel",
PrivacyUri = new Uri("https://privacy.claude.com/en/"),
},
[AIServiceType.AzureAIInference] = new AIServiceTypeMetadata
{
ServiceType = AIServiceType.AzureAIInference,
@@ -85,14 +60,6 @@ public static class AIServiceTypeRegistry
PrivacyLabel = "AdvancedPaste_Google_PrivacyLabel",
PrivacyUri = new Uri("https://support.google.com/gemini/answer/13594961"),
},
[AIServiceType.HuggingFace] = new AIServiceTypeMetadata
{
ServiceType = AIServiceType.HuggingFace,
DisplayName = "Hugging Face",
IconPath = "ms-appx:///Assets/Settings/Icons/Models/HuggingFace.svg",
IsOnlineService = true,
IsAvailableInUI = false, // Currently disabled in UI
},
[AIServiceType.Mistral] = new AIServiceTypeMetadata
{
ServiceType = AIServiceType.Mistral,

View File

@@ -87,6 +87,8 @@ namespace Microsoft.PowerToys.Settings.UI.Library
public IntProperty RecordScaling { get; set; }
public StringProperty RecordFormat { get; set; }
public BoolProperty CaptureAudio { get; set; }
public StringProperty MicrophoneDeviceId { get; set; }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -1,10 +0,0 @@
<svg width="17" height="16" viewBox="0 0 17 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_2092_1822)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.718 2.34668H12.12L16.5 13.3333H14.098L9.718 2.34668ZM4.87933 2.34668H7.39067L11.7707 13.3333H9.32133L8.426 11.026H3.84467L2.94867 13.3327H0.5L4.88 2.34801L4.87933 2.34668ZM7.634 8.98601L6.13533 5.12468L4.63667 8.98668H7.63333L7.634 8.98601Z" fill="black"/>
</g>
<defs>
<clipPath id="clip0_2092_1822">
<rect width="16" height="16" fill="white" transform="translate(0.5)"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 585 B

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 5.7 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,33 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Globalization;
using Microsoft.UI.Xaml.Data;
namespace Microsoft.PowerToys.Settings.UI.Converters
{
public partial class StringToDoubleConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value is string s && double.TryParse(s, NumberStyles.Float, CultureInfo.InvariantCulture, out double result))
{
return result;
}
return 0.0;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
if (value is double d)
{
return d.ToString(CultureInfo.InvariantCulture);
}
return "0";
}
}
}

View File

@@ -317,7 +317,6 @@ namespace Microsoft.PowerToys.Settings.UI.Views
bool requiresEndpoint = serviceKind is AIServiceType.AzureOpenAI
or AIServiceType.AzureAIInference
or AIServiceType.Mistral
or AIServiceType.HuggingFace
or AIServiceType.Ollama;
bool requiresDeployment = serviceKind == AIServiceType.AzureOpenAI;
bool requiresApiVersion = serviceKind == AIServiceType.AzureOpenAI;
@@ -874,7 +873,6 @@ namespace Microsoft.PowerToys.Settings.UI.Views
AIServiceType.AzureOpenAI => "https://your-resource.openai.azure.com/",
AIServiceType.AzureAIInference => "https://{resource-name}.cognitiveservices.azure.com/",
AIServiceType.Mistral => "https://api.mistral.ai/v1/",
AIServiceType.HuggingFace => "https://api-inference.huggingface.co/models/",
AIServiceType.Ollama => "http://localhost:11434/",
_ => "https://your-resource.openai.azure.com/",
};

View File

@@ -1,24 +1,23 @@
<?xml version="1.0" encoding="utf-8" ?>
<Page
<local:NavigablePage
x:Class="Microsoft.PowerToys.Settings.UI.Views.LightSwitchPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ViewModel="using:Microsoft.PowerToys.Settings.UI.ViewModels"
xmlns:animations="using:CommunityToolkit.WinUI.Animations"
xmlns:controls="using:Microsoft.PowerToys.Settings.UI.Controls"
xmlns:converters="using:Microsoft.PowerToys.Settings.UI.Converters"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:helpers="using:Settings.UI.Library.Helpers"
xmlns:local="using:Microsoft.PowerToys.Settings.UI.Helpers"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:tkcontrols="using:CommunityToolkit.WinUI.Controls"
xmlns:tkconverters="using:CommunityToolkit.WinUI.Converters"
xmlns:ui="using:CommunityToolkit.WinUI"
d:DataContext="{d:DesignInstance Type=ViewModel:LightSwitchViewModel}"
xmlns:viewModels="using:Microsoft.PowerToys.Settings.UI.ViewModels"
d:DataContext="{d:DesignInstance Type=viewModels:LightSwitchViewModel}"
AutomationProperties.LandmarkType="Main"
mc:Ignorable="d">
<Page.Resources>
<local:NavigablePage.Resources>
<converters:TimeSpanToFriendlyTimeConverter x:Key="TimeSpanToFriendlyTimeConverter" />
</Page.Resources>
<converters:StringToDoubleConverter x:Key="StringToDoubleConverter" />
</local:NavigablePage.Resources>
<Grid>
<controls:SettingsPageControl
x:Uid="LightSwitch"
@@ -88,16 +87,14 @@
x:Uid="LightSwitch_LocationSettingsCard"
Visibility="Collapsed">
<StackPanel Orientation="Horizontal" Spacing="8">
<TextBlock
<controls:IsEnabledTextBlock
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Text="{x:Bind ViewModel.SyncButtonInformation, Mode=OneWay}" />
<Button
Padding="8"
x:Uid="LightSwitch_SetLocationButton"
AutomationProperties.AutomationId="SetLocationButton_LightSwitch"
Click="SyncLocationButton_Click"
Content="{ui:FontIcon Glyph=&#xECAF;,
FontSize=16}" />
Click="SyncLocationButton_Click" />
</StackPanel>
</tkcontrols:SettingsCard>
@@ -202,17 +199,22 @@
<ContentDialog
x:Name="LocationDialog"
x:Uid="LightSwitch_LocationDialog"
IsPrimaryButtonEnabled="True"
Closed="LocationDialog_Closed"
IsPrimaryButtonEnabled="False"
IsSecondaryButtonEnabled="True"
Opened="LocationDialog_Opened"
PrimaryButtonClick="LocationDialog_PrimaryButtonClick"
PrimaryButtonStyle="{StaticResource AccentButtonStyle}">
<Grid RowSpacing="48">
<Grid RowSpacing="16">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" MinHeight="64" />
</Grid.RowDefinitions>
<TextBlock x:Uid="LightSwitch_LocationDialog_Description" Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
<TextBlock
x:Uid="LightSwitch_LocationDialog_Description"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
TextWrapping="Wrap" />
<!--<AutoSuggestBox
x:Name="CityAutoSuggestBox"
Grid.Row="1"
@@ -240,80 +242,84 @@
</DataTemplate>
</AutoSuggestBox.ItemTemplate>
</AutoSuggestBox>-->
<StackPanel
Grid.Row="2"
Margin="0,24,0,0"
HorizontalAlignment="Center"
Orientation="Vertical"
Spacing="32">
<Grid
Grid.Row="1"
Margin="0,12,0,0"
ColumnSpacing="4">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="124" />
<ColumnDefinition Width="124" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<NumberBox
x:Name="LatitudeBox"
x:Uid="LightSwitch_LatitudeBox"
AutomationProperties.AutomationId="LatitudeBox_LightSwitch"
Maximum="90"
Minimum="-90"
ValueChanged="LatLonBox_ValueChanged"
Value="{x:Bind ViewModel.LocationPanelLatitude, Mode=TwoWay}" />
<NumberBox
x:Name="LongitudeBox"
x:Uid="LightSwitch_LongitudeBox"
Grid.Column="1"
AutomationProperties.AutomationId="LongitudeBox_LightSwitch"
Maximum="180"
Minimum="-180"
ValueChanged="LatLonBox_ValueChanged"
Value="{x:Bind ViewModel.LocationPanelLongitude, Mode=TwoWay}" />
<Button
x:Name="SyncButton"
x:Uid="LightSwitch_FindLocationAutomation"
Grid.Column="2"
Margin="4,0,0,0"
HorizontalAlignment="Stretch"
VerticalAlignment="Bottom"
AutomationProperties.AutomationId="SyncLocationButton_LightSwitch"
Style="{StaticResource AccentButtonStyle}"
Visibility="Collapsed">
Click="GetGeoLocation_Click">
<StackPanel Orientation="Horizontal" Spacing="8">
<FontIcon FontSize="14" Glyph="&#xECAF;" />
<TextBlock x:Uid="LightSwitch_GetCurrentLocation" />
<FontIcon FontSize="16" Glyph="&#xECAF;" />
<TextBlock x:Uid="LightSwitch_FindLocation" />
</StackPanel>
</Button>
</StackPanel>
</Grid>
<ProgressRing
x:Name="SyncLoader"
Grid.Row="1"
Grid.Row="2"
Width="40"
Height="40"
VerticalAlignment="Center"
IsActive="False"
Visibility="Collapsed" />
<Grid
x:Name="LocationResultPanel"
Grid.Row="1"
Grid.Row="2"
VerticalAlignment="Bottom"
ColumnSpacing="16"
RowSpacing="12"
Visibility="Collapsed">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<FontIcon FontSize="16" Glyph="&#xECAF;">
<ToolTipService.ToolTip>
<TextBlock x:Uid="LightSwitch_LocationTooltip" />
</ToolTipService.ToolTip>
</FontIcon>
<TextBlock
Grid.Row="1"
AutomationProperties.AutomationId="LocationResultText_LightSwitch"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
TextAlignment="Center">
<Run Text="{x:Bind ViewModel.Latitude, Mode=OneWay}" /><Run Text="°, " />
<Run Text="{x:Bind ViewModel.Longitude, Mode=OneWay}" /><Run Text="°" />
</TextBlock>
<FontIcon
Grid.Column="1"
FontSize="20"
Glyph="&#xED39;">
<FontIcon FontSize="20" Glyph="&#xED39;">
<ToolTipService.ToolTip>
<TextBlock x:Uid="LightSwitch_SunriseTooltip" />
</ToolTipService.ToolTip>
</FontIcon>
<TextBlock
Grid.Row="1"
Grid.Column="1"
AutomationProperties.AutomationId="SunriseText_LightSwitch"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Text="{x:Bind ViewModel.LightTimeTimeSpan, Converter={StaticResource TimeSpanToFriendlyTimeConverter}, Mode=OneWay}"
TextAlignment="Center" />
Text="{x:Bind ViewModel.LocationPanelLightTime, Converter={StaticResource TimeSpanToFriendlyTimeConverter}, Mode=OneWay}"
TextAlignment="Left" />
<FontIcon
Grid.Column="2"
Grid.Row="1"
FontSize="20"
Glyph="&#xED3A;">
<ToolTipService.ToolTip>
@@ -321,12 +327,12 @@
</ToolTipService.ToolTip>
</FontIcon>
<TextBlock
Grid.Row="2"
Grid.Column="2"
Grid.Row="1"
Grid.Column="1"
AutomationProperties.AutomationId="SunsetText_LightSwitch"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Text="{x:Bind ViewModel.DarkTimeTimeSpan, Converter={StaticResource TimeSpanToFriendlyTimeConverter}, Mode=OneWay}"
TextAlignment="Center" />
Text="{x:Bind ViewModel.LocationPanelDarkTime, Converter={StaticResource TimeSpanToFriendlyTimeConverter}, Mode=OneWay}"
TextAlignment="Left" />
</Grid>
</Grid>
</ContentDialog>
@@ -358,4 +364,4 @@
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
</Page>
</local:NavigablePage>

View File

@@ -12,88 +12,97 @@ using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Helpers;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Settings.UI.Library.Helpers;
using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
using Microsoft.PowerToys.Settings.UI.ViewModels;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using PowerToys.GPOWrapper;
using Settings.UI.Library;
using Settings.UI.Library.Helpers;
using Windows.Devices.Geolocation;
using Windows.Services.Maps;
namespace Microsoft.PowerToys.Settings.UI.Views
{
public sealed partial class LightSwitchPage : Page
public sealed partial class LightSwitchPage : NavigablePage, IRefreshablePage
{
private readonly string _appName = "LightSwitch";
private readonly SettingsUtils _settingsUtils;
private readonly Func<string, int> _sendConfigMsg = ShellPage.SendDefaultIPCMessage;
private readonly string appName = "LightSwitch";
private readonly SettingsUtils settingsUtils;
private readonly Func<string, int> sendConfigMsg = ShellPage.SendDefaultIPCMessage;
private readonly ISettingsRepository<GeneralSettings> _generalSettingsRepository;
private readonly ISettingsRepository<LightSwitchSettings> _moduleSettingsRepository;
private readonly SettingsRepository<GeneralSettings> generalSettingsRepository;
private readonly SettingsRepository<LightSwitchSettings> moduleSettingsRepository;
private readonly IFileSystem _fileSystem;
private readonly IFileSystemWatcher _fileSystemWatcher;
private readonly DispatcherQueue _dispatcherQueue;
private readonly IFileSystem fileSystem;
private readonly IFileSystemWatcher fileSystemWatcher;
private readonly DispatcherQueue dispatcherQueue;
private bool suppressViewModelUpdates;
private bool suppressLatLonChange = true;
private bool latBoxLoaded;
private bool lonBoxLoaded;
private LightSwitchViewModel ViewModel { get; set; }
public LightSwitchPage()
{
_settingsUtils = new SettingsUtils();
_sendConfigMsg = ShellPage.SendDefaultIPCMessage;
this.settingsUtils = new SettingsUtils();
this.sendConfigMsg = ShellPage.SendDefaultIPCMessage;
_generalSettingsRepository = SettingsRepository<GeneralSettings>.GetInstance(_settingsUtils);
_moduleSettingsRepository = SettingsRepository<LightSwitchSettings>.GetInstance(_settingsUtils);
this.generalSettingsRepository = SettingsRepository<GeneralSettings>.GetInstance(this.settingsUtils);
this.moduleSettingsRepository = SettingsRepository<LightSwitchSettings>.GetInstance(this.settingsUtils);
// Get settings from JSON (or defaults if JSON missing)
var darkSettings = _moduleSettingsRepository.SettingsConfig;
var darkSettings = this.moduleSettingsRepository.SettingsConfig;
// Pass them into the ViewModel
ViewModel = new LightSwitchViewModel(darkSettings, ShellPage.SendDefaultIPCMessage);
ViewModel.PropertyChanged += ViewModel_PropertyChanged;
this.ViewModel = new LightSwitchViewModel(darkSettings, this.sendConfigMsg);
this.ViewModel.PropertyChanged += ViewModel_PropertyChanged;
LoadSettings(_generalSettingsRepository, _moduleSettingsRepository);
this.LoadSettings(this.generalSettingsRepository, this.moduleSettingsRepository);
DataContext = ViewModel;
DataContext = this.ViewModel;
var settingsPath = _settingsUtils.GetSettingsFilePath(_appName);
var settingsPath = this.settingsUtils.GetSettingsFilePath(this.appName);
_dispatcherQueue = DispatcherQueue.GetForCurrentThread();
_fileSystem = new FileSystem();
this.dispatcherQueue = DispatcherQueue.GetForCurrentThread();
this.fileSystem = new FileSystem();
_fileSystemWatcher = _fileSystem.FileSystemWatcher.New();
_fileSystemWatcher.Path = _fileSystem.Path.GetDirectoryName(settingsPath);
_fileSystemWatcher.Filter = _fileSystem.Path.GetFileName(settingsPath);
_fileSystemWatcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.CreationTime;
_fileSystemWatcher.Changed += Settings_Changed;
_fileSystemWatcher.EnableRaisingEvents = true;
this.fileSystemWatcher = this.fileSystem.FileSystemWatcher.New();
this.fileSystemWatcher.Path = this.fileSystem.Path.GetDirectoryName(settingsPath);
this.fileSystemWatcher.Filter = this.fileSystem.Path.GetFileName(settingsPath);
this.fileSystemWatcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.CreationTime;
this.fileSystemWatcher.Changed += Settings_Changed;
this.fileSystemWatcher.EnableRaisingEvents = true;
this.InitializeComponent();
this.Loaded += LightSwitchPage_Loaded;
this.Loaded += (s, e) => ViewModel.OnPageLoaded();
Loaded += LightSwitchPage_Loaded;
Loaded += (s, e) => this.ViewModel.OnPageLoaded();
}
public void RefreshEnabledState()
{
this.ViewModel.RefreshEnabledState();
}
private void LightSwitchPage_Loaded(object sender, RoutedEventArgs e)
{
if (ViewModel.SearchLocations.Count == 0)
if (this.ViewModel.SearchLocations.Count == 0)
{
foreach (var city in SearchLocationLoader.GetAll())
{
ViewModel.SearchLocations.Add(city);
this.ViewModel.SearchLocations.Add(city);
}
}
ViewModel.InitializeScheduleMode();
this.ViewModel.InitializeScheduleMode();
}
private async Task GetGeoLocation()
private async void GetGeoLocation_Click(object sender, RoutedEventArgs e)
{
SyncButton.IsEnabled = false;
SyncLoader.IsActive = true;
SyncLoader.Visibility = Visibility.Visible;
this.LatitudeBox.IsEnabled = false;
this.LongitudeBox.IsEnabled = false;
this.SyncButton.IsEnabled = false;
this.SyncLoader.IsActive = true;
this.SyncLoader.Visibility = Visibility.Visible;
this.LocationResultPanel.Visibility = Visibility.Collapsed;
try
{
@@ -112,75 +121,157 @@ namespace Microsoft.PowerToys.Settings.UI.Views
double latitude = Math.Round(pos.Coordinate.Point.Position.Latitude);
double longitude = Math.Round(pos.Coordinate.Point.Position.Longitude);
SunTimes result = SunCalc.CalculateSunriseSunset(
latitude,
longitude,
DateTime.Now.Year,
DateTime.Now.Month,
DateTime.Now.Day);
ViewModel.LocationPanelLatitude = latitude;
ViewModel.LocationPanelLongitude = longitude;
ViewModel.LightTime = (result.SunriseHour * 60) + result.SunriseMinute;
ViewModel.DarkTime = (result.SunsetHour * 60) + result.SunsetMinute;
ViewModel.Latitude = latitude.ToString(CultureInfo.InvariantCulture);
ViewModel.Longitude = longitude.ToString(CultureInfo.InvariantCulture);
var result = SunCalc.CalculateSunriseSunset(latitude, longitude, DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day);
this.ViewModel.LocationPanelLightTimeMinutes = (result.SunriseHour * 60) + result.SunriseMinute;
this.ViewModel.LocationPanelDarkTimeMinutes = (result.SunsetHour * 60) + result.SunsetMinute;
// Since we use this mode, we can remove the selected city data.
ViewModel.SelectedCity = null;
this.ViewModel.SelectedCity = null;
// CityAutoSuggestBox.Text = string.Empty;
ViewModel.SyncButtonInformation = $"{ViewModel.Latitude}<7D>, {ViewModel.Longitude}<7D>";
this.suppressLatLonChange = false;
// ViewModel.CityTimesText = $"Sunrise: {result.SunriseHour}:{result.SunriseMinute:D2}\n" + $"Sunset: {result.SunsetHour}:{result.SunsetMinute:D2}";
SyncButton.IsEnabled = true;
SyncLoader.IsActive = false;
SyncLoader.Visibility = Visibility.Collapsed;
LocationDialog.IsPrimaryButtonEnabled = true;
LocationResultPanel.Visibility = Visibility.Visible;
this.SyncButton.IsEnabled = true;
this.SyncLoader.IsActive = false;
this.SyncLoader.Visibility = Visibility.Collapsed;
this.LocationDialog.IsPrimaryButtonEnabled = true;
this.LatitudeBox.IsEnabled = true;
this.LongitudeBox.IsEnabled = true;
this.LocationResultPanel.Visibility = Visibility.Visible;
}
catch (Exception ex)
{
SyncButton.IsEnabled = true;
SyncLoader.IsActive = false;
System.Diagnostics.Debug.WriteLine("Location error: " + ex.Message);
this.SyncButton.IsEnabled = true;
this.SyncLoader.IsActive = false;
this.SyncLoader.Visibility = Visibility.Collapsed;
this.LocationResultPanel.Visibility = Visibility.Collapsed;
this.LatitudeBox.IsEnabled = true;
this.LongitudeBox.IsEnabled = true;
Logger.LogInfo($"Location error: " + ex.Message);
}
}
private void LatLonBox_ValueChanged(NumberBox sender, NumberBoxValueChangedEventArgs args)
{
if (this.suppressLatLonChange)
{
return;
}
double latitude = this.LatitudeBox.Value;
double longitude = this.LongitudeBox.Value;
if (double.IsNaN(latitude) || double.IsNaN(longitude))
{
return;
}
double viewModelLatitude = double.TryParse(this.ViewModel.Latitude, out var lat) ? lat : 0.0;
double viewModelLongitude = double.TryParse(this.ViewModel.Longitude, out var lon) ? lon : 0.0;
if (Math.Abs(latitude - viewModelLatitude) < 0.0001 && Math.Abs(longitude - viewModelLongitude) < 0.0001)
{
return;
}
var result = SunCalc.CalculateSunriseSunset(latitude, longitude, DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day);
this.ViewModel.LocationPanelLightTimeMinutes = (result.SunriseHour * 60) + result.SunriseMinute;
this.ViewModel.LocationPanelDarkTimeMinutes = (result.SunsetHour * 60) + result.SunsetMinute;
// Show the panel with these values
this.LocationResultPanel.Visibility = Visibility.Visible;
if (this.LocationDialog != null)
{
this.LocationDialog.IsPrimaryButtonEnabled = true;
}
}
private void LocationDialog_PrimaryButtonClick(object sender, ContentDialogButtonClickEventArgs args)
{
if (ViewModel.ScheduleMode == "SunriseToSunsetUser")
if (double.IsNaN(this.LatitudeBox.Value) || double.IsNaN(this.LongitudeBox.Value))
{
ViewModel.SyncButtonInformation = ViewModel.SelectedCity.City;
}
else if (ViewModel.ScheduleMode == "SunriseToSunsetGeo")
{
ViewModel.SyncButtonInformation = $"{ViewModel.Latitude}<7D>, {ViewModel.Longitude}<7D>";
return;
}
SunriseModeChartState();
double latitude = this.LatitudeBox.Value;
double longitude = this.LongitudeBox.Value;
// need to save the values
this.ViewModel.Latitude = latitude.ToString(CultureInfo.InvariantCulture);
this.ViewModel.Longitude = longitude.ToString(CultureInfo.InvariantCulture);
this.ViewModel.SyncButtonInformation = $"{this.ViewModel.Latitude}<7D>, {this.ViewModel.Longitude}<7D>";
var result = SunCalc.CalculateSunriseSunset(latitude, longitude, DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day);
this.ViewModel.LightTime = (result.SunriseHour * 60) + result.SunriseMinute;
this.ViewModel.DarkTime = (result.SunsetHour * 60) + result.SunsetMinute;
this.SunriseModeChartState();
}
private void LocationDialog_Opened(ContentDialog sender, ContentDialogOpenedEventArgs args)
{
this.LatitudeBox.Loaded += LatLonBox_Loaded;
this.LongitudeBox.Loaded += LatLonBox_Loaded;
}
private void LocationDialog_Closed(ContentDialog sender, ContentDialogClosedEventArgs args)
{
this.LatitudeBox.Loaded -= LatLonBox_Loaded;
this.LongitudeBox.Loaded -= LatLonBox_Loaded;
this.latBoxLoaded = false;
this.lonBoxLoaded = false;
}
private void LatLonBox_Loaded(object sender, RoutedEventArgs e)
{
if (sender is NumberBox numberBox && numberBox == this.LatitudeBox && this.LatitudeBox.IsLoaded)
{
this.latBoxLoaded = true;
}
else if (sender is NumberBox numberBox2 && numberBox2 == this.LongitudeBox && this.LongitudeBox.IsLoaded)
{
this.lonBoxLoaded = true;
}
if (this.latBoxLoaded && this.lonBoxLoaded)
{
this.suppressLatLonChange = false;
}
}
private void ViewModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (this.suppressViewModelUpdates)
{
return;
}
if (e.PropertyName == "IsEnabled")
{
if (ViewModel.IsEnabled != _generalSettingsRepository.SettingsConfig.Enabled.LightSwitch)
if (this.ViewModel.IsEnabled != this.generalSettingsRepository.SettingsConfig.Enabled.LightSwitch)
{
_generalSettingsRepository.SettingsConfig.Enabled.LightSwitch = ViewModel.IsEnabled;
this.generalSettingsRepository.SettingsConfig.Enabled.LightSwitch = this.ViewModel.IsEnabled;
var generalSettingsMessage = new OutGoingGeneralSettings(_generalSettingsRepository.SettingsConfig).ToString();
var generalSettingsMessage = new OutGoingGeneralSettings(this.generalSettingsRepository.SettingsConfig).ToString();
Logger.LogInfo($"Saved general settings from Light Switch page.");
_sendConfigMsg?.Invoke(generalSettingsMessage);
this.sendConfigMsg?.Invoke(generalSettingsMessage);
}
}
else
{
if (ViewModel.ModuleSettings != null)
if (this.ViewModel.ModuleSettings != null)
{
SndLightSwitchSettings currentSettings = new(_moduleSettingsRepository.SettingsConfig);
SndLightSwitchSettings currentSettings = new(this.moduleSettingsRepository.SettingsConfig);
SndModuleSettings<SndLightSwitchSettings> csIpcMessage = new(currentSettings);
SndLightSwitchSettings outSettings = new(ViewModel.ModuleSettings);
SndLightSwitchSettings outSettings = new(this.ViewModel.ModuleSettings);
SndModuleSettings<SndLightSwitchSettings> outIpcMessage = new(outSettings);
string csMessage = csIpcMessage.ToJsonString();
@@ -190,13 +281,13 @@ namespace Microsoft.PowerToys.Settings.UI.Views
{
Logger.LogInfo($"Saved Light Switch settings from Light Switch page.");
_sendConfigMsg?.Invoke(outMessage);
this.sendConfigMsg?.Invoke(outMessage);
}
}
}
}
private void LoadSettings(ISettingsRepository<GeneralSettings> generalSettingsRepository, ISettingsRepository<LightSwitchSettings> moduleSettingsRepository)
private void LoadSettings(SettingsRepository<GeneralSettings> generalSettingsRepository, SettingsRepository<LightSwitchSettings> moduleSettingsRepository)
{
if (generalSettingsRepository != null)
{
@@ -221,8 +312,8 @@ namespace Microsoft.PowerToys.Settings.UI.Views
{
if (generalSettings != null)
{
ViewModel.IsEnabled = generalSettings.Enabled.LightSwitch;
ViewModel.ModuleSettings = (LightSwitchSettings)lightSwitchSettings.Clone();
this.ViewModel.IsEnabled = generalSettings.Enabled.LightSwitch;
this.ViewModel.ModuleSettings = (LightSwitchSettings)lightSwitchSettings.Clone();
UpdateEnabledState(generalSettings.Enabled.LightSwitch);
}
@@ -239,10 +330,14 @@ namespace Microsoft.PowerToys.Settings.UI.Views
private void Settings_Changed(object sender, FileSystemEventArgs e)
{
_dispatcherQueue.TryEnqueue(() =>
this.dispatcherQueue.TryEnqueue(() =>
{
_moduleSettingsRepository.ReloadSettings();
LoadSettings(_generalSettingsRepository, _moduleSettingsRepository);
this.suppressViewModelUpdates = true;
this.moduleSettingsRepository.ReloadSettings();
this.LoadSettings(this.generalSettingsRepository, this.moduleSettingsRepository);
this.suppressViewModelUpdates = false;
});
}
@@ -253,20 +348,20 @@ namespace Microsoft.PowerToys.Settings.UI.Views
if (enabledGpoRuleConfiguration == GpoRuleConfigured.Disabled || enabledGpoRuleConfiguration == GpoRuleConfigured.Enabled)
{
// Get the enabled state from GPO.
ViewModel.IsEnabledGpoConfigured = true;
ViewModel.EnabledGPOConfiguration = enabledGpoRuleConfiguration == GpoRuleConfigured.Enabled;
this.ViewModel.IsEnabledGpoConfigured = true;
this.ViewModel.EnabledGPOConfiguration = enabledGpoRuleConfiguration == GpoRuleConfigured.Enabled;
}
else
{
ViewModel.IsEnabled = recommendedState;
this.ViewModel.IsEnabled = recommendedState;
}
}
private async void SyncLocationButton_Click(object sender, RoutedEventArgs e)
{
LocationDialog.IsPrimaryButtonEnabled = false;
LocationResultPanel.Visibility = Visibility.Collapsed;
await LocationDialog.ShowAsync();
this.LocationDialog.IsPrimaryButtonEnabled = false;
this.LocationResultPanel.Visibility = Visibility.Collapsed;
await this.LocationDialog.ShowAsync();
}
private void CityAutoSuggestBox_TextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args)
@@ -276,7 +371,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views
string query = sender.Text.ToLower(CultureInfo.CurrentCulture);
// Filter your cities (assuming ViewModel.Cities is a List<City>)
var filtered = ViewModel.SearchLocations
var filtered = this.ViewModel.SearchLocations
.Where(c =>
(c.City?.Contains(query, StringComparison.CurrentCultureIgnoreCase) ?? false) ||
(c.Country?.Contains(query, StringComparison.CurrentCultureIgnoreCase) ?? false))
@@ -286,7 +381,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views
}
}
private void CityAutoSuggestBox_SuggestionChosen(AutoSuggestBox sender, AutoSuggestBoxSuggestionChosenEventArgs args)
/* private void CityAutoSuggestBox_SuggestionChosen(AutoSuggestBox sender, AutoSuggestBoxSuggestionChosenEventArgs args)
{
if (args.SelectedItem is SearchLocation location)
{
@@ -296,43 +391,38 @@ namespace Microsoft.PowerToys.Settings.UI.Views
LocationDialog.IsPrimaryButtonEnabled = true;
LocationResultPanel.Visibility = Visibility.Visible;
}
}
} */
private void ModeSelector_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
switch (ViewModel.ScheduleMode)
switch (this.ViewModel.ScheduleMode)
{
case "FixedHours":
VisualStateManager.GoToState(this, "ManualState", true);
TimelineCard.Visibility = Visibility.Visible;
this.TimelineCard.Visibility = Visibility.Visible;
break;
case "SunsetToSunrise":
VisualStateManager.GoToState(this, "SunsetToSunriseState", true);
SunriseModeChartState();
this.SunriseModeChartState();
break;
default:
VisualStateManager.GoToState(this, "OffState", true);
TimelineCard.Visibility = Visibility.Collapsed;
this.TimelineCard.Visibility = Visibility.Collapsed;
break;
}
}
private async void LocationDialog_Opened(ContentDialog sender, ContentDialogOpenedEventArgs args)
{
await GetGeoLocation();
}
private void SunriseModeChartState()
{
if (ViewModel.Latitude != "0.0" && ViewModel.Longitude != "0.0")
if (this.ViewModel.Latitude != "0.0" && this.ViewModel.Longitude != "0.0")
{
TimelineCard.Visibility = Visibility.Visible;
LocationWarningBar.Visibility = Visibility.Collapsed;
this.TimelineCard.Visibility = Visibility.Visible;
this.LocationWarningBar.Visibility = Visibility.Collapsed;
}
else
{
TimelineCard.Visibility = Visibility.Collapsed;
LocationWarningBar.Visibility = Visibility.Visible;
this.TimelineCard.Visibility = Visibility.Collapsed;
this.LocationWarningBar.Visibility = Visibility.Visible;
}
}
}

View File

@@ -241,6 +241,12 @@
<ComboBoxItem>1.0</ComboBoxItem>
</ComboBox>
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard Name="ZoomItRecordFormat" x:Uid="ZoomIt_Record_Format">
<ComboBox MinWidth="{StaticResource SettingActionControlMinWidth}" SelectedIndex="{x:Bind Path=ViewModel.RecordFormatIndex, Mode=TwoWay}">
<ComboBoxItem>GIF</ComboBoxItem>
<ComboBoxItem>MP4</ComboBoxItem>
</ComboBox>
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard Name="ZoomItRecordCaptureAudio" x:Uid="ZoomIt_Record_CaptureAudio">
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.RecordCaptureAudio, Mode=TwoWay}" />
</tkcontrols:SettingsCard>

View File

@@ -1,17 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
@@ -26,36 +26,36 @@
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
@@ -708,15 +708,6 @@ Please review the placeholder content that represents the final terms and usage
<data name="AdvancedPaste_Google_PrivacyLabel" xml:space="preserve">
<value>Google Privacy Policy</value>
</data>
<data name="AdvancedPaste_Anthropic_LegalDescription" xml:space="preserve">
<value>Your API key connects directly to Anthropic services. By setting up this provider, you agree to comply with Anthropic's usage policies and data handling practices.</value>
</data>
<data name="AdvancedPaste_Anthropic_TermsLabel" xml:space="preserve">
<value>Anthropic Terms of Service</value>
</data>
<data name="AdvancedPaste_Anthropic_PrivacyLabel" xml:space="preserve">
<value>Anthropic Privacy Policy</value>
</data>
<data name="AdvancedPaste_Mistral_LegalDescription" xml:space="preserve">
<value>Your API key connects directly to Mistral services. By setting up this provider, you agree to comply with Mistral's usage policies and data handling practices.</value>
</data>
@@ -726,15 +717,6 @@ Please review the placeholder content that represents the final terms and usage
<data name="AdvancedPaste_Mistral_PrivacyLabel" xml:space="preserve">
<value>Mistral Privacy Policy</value>
</data>
<data name="AdvancedPaste_AmazonBedrock_LegalDescription" xml:space="preserve">
<value>Your API key connects directly to Amazon services. By setting up this provider, you agree to comply with Amazon's usage policies and data handling practices.</value>
</data>
<data name="AdvancedPaste_AmazonBedrock_TermsLabel" xml:space="preserve">
<value>AWS Service Terms</value>
</data>
<data name="AdvancedPaste_AmazonBedrock_PrivacyLabel" xml:space="preserve">
<value>AWS Privacy Notice</value>
</data>
<data name="AdvancedPaste_Ollama_TermsLabel" xml:space="preserve">
<value>Ollama Terms of Service</value>
</data>
@@ -2269,7 +2251,7 @@ Take a moment to preview the various utilities listed or view our comprehensive
<value>Press the **Restart as administrator** button from the File Locksmith UI to also get information on elevated processes that might be using the files.</value>
</data>
<data name="Oobe_FileExplorer_HowToEnable.Text" xml:space="preserve">
<value>Select **View** which is located at the top of File Explorer, followed by **Show**, and then **Preview pane**.
<value>Select **View** which is located at the top of File Explorer, followed by **Show**, and then **Preview pane**.
From there, simply click on one of the supported files in the File Explorer and observe the content on the preview pane!</value>
</data>
<data name="Oobe_HowToCreateMappings.Text" xml:space="preserve">
@@ -2695,9 +2677,7 @@ From there, simply click on one of the supported files in the File Explorer and
<data name="Oobe_MouseUtils_MouseHighlighter_Description.Text" xml:space="preserve">
<value>Use a keyboard shortcut to highlight left and right mouse clicks.</value>
<comment>Mouse as in the hardware peripheral.</comment>
</data>
<!-- CursorWrap Module -->
</data>
<data name="MouseUtils_Enable_CursorWrap.Header" xml:space="preserve">
<value>Enable CursorWrap</value>
</data>
@@ -2707,8 +2687,6 @@ From there, simply click on one of the supported files in the File Explorer and
<data name="MouseUtils_CursorWrap.Description" xml:space="preserve">
<value>Wrap the mouse cursor between monitor edges</value>
</data>
<!-- Activation Shortcut -->
<data name="MouseUtils_CursorWrap_ActivationShortcut.Header" xml:space="preserve">
<value>Activation shortcut</value>
</data>
@@ -2718,19 +2696,15 @@ From there, simply click on one of the supported files in the File Explorer and
<data name="MouseUtils_CursorWrap_ActivationShortcut_Button.Content" xml:space="preserve">
<value>Set shortcut</value>
</data>
<data name="MouseUtils_CursorWrap_DisableWrapDuringDrag.Content" xml:space="preserve">
<value>Disable wrapping while dragging</value>
</data>
<!-- Auto-activate -->
</data>
<data name="MouseUtils_CursorWrap_AutoActivate.Header" xml:space="preserve">
<value>Auto-activate on startup</value>
</data>
<data name="MouseUtils_CursorWrap_AutoActivate.Content" xml:space="preserve">
<value>Automatically activate on utility startup</value>
</data>
</data>
<data name="Oobe_MouseUtils_MousePointerCrosshairs.Text" xml:space="preserve">
<value>Mouse Pointer Crosshairs</value>
<comment>Mouse as in the hardware peripheral.</comment>
@@ -5046,6 +5020,9 @@ To record a specific window, enter the hotkey with the Alt key in the opposite m
<data name="ZoomIt_Record_Scaling.Header" xml:space="preserve">
<value>Scaling</value>
</data>
<data name="ZoomIt_Record_Format.Header" xml:space="preserve">
<value>Format</value>
</data>
<data name="ZoomIt_Record_CaptureAudio.Header" xml:space="preserve">
<value>Capture audio input</value>
</data>
@@ -5480,16 +5457,13 @@ To record a specific window, enter the hotkey with the Alt key in the opposite m
<value>Select a location</value>
</data>
<data name="LightSwitch_LocationDialog.PrimaryButtonText" xml:space="preserve">
<value>Select</value>
<value>Save</value>
</data>
<data name="LightSwitch_LocationDialog.SecondaryButtonText" xml:space="preserve">
<value>Cancel</value>
</data>
<data name="LightSwitch_GetCurrentLocation.Text" xml:space="preserve">
<value>Get current location</value>
</data>
<data name="LightSwitch_LocationDialog_Description.Text" xml:space="preserve">
<value>To calculate the sunrise and sunset, Light Switch needs a location.</value>
<value>Detect your location automatically or enter it manually to calculate sunrise and sunset times.</value>
</data>
<data name="LightSwitch_SunriseText.Text" xml:space="preserve">
<value>Sunrise</value>
@@ -5497,8 +5471,17 @@ To record a specific window, enter the hotkey with the Alt key in the opposite m
<data name="LightSwitch_SunsetText.Text" xml:space="preserve">
<value>Sunset</value>
</data>
<data name="LightSwitch_LocationTooltip.Text" xml:space="preserve">
<value>Location</value>
<data name="LightSwitch_LatitudeBox.Header" xml:space="preserve">
<value>Latitude</value>
</data>
<data name="LightSwitch_LongitudeBox.Header" xml:space="preserve">
<value>Longitude</value>
</data>
<data name="LightSwitch_FindLocation.Text" xml:space="preserve">
<value>Detect location</value>
</data>
<data name="LightSwitch_FindLocationAutomation.[using:Microsoft.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
<value>Detect location</value>
</data>
<data name="LightSwitch_SunriseTooltip.Text" xml:space="preserve">
<value>Sunrise</value>
@@ -5582,7 +5565,7 @@ To record a specific window, enter the hotkey with the Alt key in the opposite m
</data>
<data name="ShortcutConflictControl_Automation.[using:Microsoft.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
<value>Shortcut conflicts</value>
</data>
</data>
<data name="ShortcutConflictControl_NoConflictsFound" xml:space="preserve">
<value>No conflicts found</value>
</data>
@@ -5693,4 +5676,7 @@ To record a specific window, enter the hotkey with the Alt key in the opposite m
<data name="Hosts_Backup_CountInput_Header" xml:space="preserve">
<value>Backup count</value>
</data>
<data name="LightSwitch_SetLocationButton.Content" xml:space="preserve">
<value>Set Location</value>
</data>
</root>

View File

@@ -10,10 +10,8 @@ using System.ComponentModel;
using System.Globalization;
using System.IO.Abstractions;
using System.Linq;
using System.Reflection;
using System.Runtime.Versioning;
using System.Text.Json;
using System.Text.Json.Serialization;
using global::PowerToys.GPOWrapper;
using Microsoft.PowerToys.Settings.UI.Helpers;
@@ -57,6 +55,16 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
private Func<string, int> SendConfigMSG { get; }
private static readonly HashSet<string> CustomActionNonPersistedProperties = new(StringComparer.Ordinal)
{
nameof(AdvancedPasteCustomAction.CanMoveUp),
nameof(AdvancedPasteCustomAction.CanMoveDown),
nameof(AdvancedPasteCustomAction.IsValid),
nameof(AdvancedPasteCustomAction.HasConflict),
nameof(AdvancedPasteCustomAction.Tooltip),
nameof(AdvancedPasteCustomAction.SubActions),
};
public AdvancedPasteViewModel(
ISettingsUtils settingsUtils,
ISettingsRepository<GeneralSettings> settingsRepository,
@@ -558,11 +566,9 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
AIServiceType.OpenAI => "gpt-4",
AIServiceType.AzureOpenAI => "gpt-4",
AIServiceType.Mistral => "mistral-large-latest",
AIServiceType.Google => "gemini-1.5-pro",
AIServiceType.Google => "gemini-2.5-pro",
AIServiceType.AzureAIInference => "gpt-4o-mini",
AIServiceType.Ollama => "llama3",
AIServiceType.Anthropic => "claude-3-5-sonnet",
AIServiceType.AmazonBedrock => "anthropic.claude-3-haiku",
_ => string.Empty,
};
}
@@ -589,7 +595,6 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
AIServiceType.AzureAIInference => GPOWrapper.GetAllowedAdvancedPasteAzureAIInferenceValue(),
AIServiceType.Mistral => GPOWrapper.GetAllowedAdvancedPasteMistralValue(),
AIServiceType.Google => GPOWrapper.GetAllowedAdvancedPasteGoogleValue(),
AIServiceType.Anthropic => GPOWrapper.GetAllowedAdvancedPasteAnthropicValue(),
_ => GpoRuleConfigured.Unavailable,
};
@@ -844,9 +849,6 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
"azureaiinference" => "https://azure.microsoft.com/products/ai-services/ai-inference",
"mistral" => "https://console.mistral.ai/account/api-keys",
"google" => "https://ai.google.dev/",
"huggingface" => "https://huggingface.co/settings/tokens",
"anthropic" => "https://console.anthropic.com/account/keys",
"amazonbedrock" => "https://aws.amazon.com/bedrock/",
"ollama" => "https://ollama.com/",
_ => "https://platform.openai.com/api-keys",
};
@@ -1000,7 +1002,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
private void OnCustomActionPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (typeof(AdvancedPasteCustomAction).GetProperty(e.PropertyName).GetCustomAttribute<JsonIgnoreAttribute>() == null)
if (!string.IsNullOrEmpty(e.PropertyName) && !CustomActionNonPersistedProperties.Contains(e.PropertyName))
{
SaveCustomActions();
}

View File

@@ -407,6 +407,71 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
}
}
private double _locationPanelLatitude;
private double _locationPanelLongitude;
public double LocationPanelLatitude
{
get => _locationPanelLatitude;
set
{
if (_locationPanelLatitude != value)
{
_locationPanelLatitude = value;
NotifyPropertyChanged();
NotifyPropertyChanged(nameof(LocationPanelLightTime));
}
}
}
public double LocationPanelLongitude
{
get => _locationPanelLongitude;
set
{
if (_locationPanelLongitude != value)
{
_locationPanelLongitude = value;
NotifyPropertyChanged();
}
}
}
private int _locationPanelLightTime;
private int _locationPanelDarkTime;
public int LocationPanelLightTimeMinutes
{
get => _locationPanelLightTime;
set
{
if (_locationPanelLightTime != value)
{
_locationPanelLightTime = value;
NotifyPropertyChanged();
NotifyPropertyChanged(nameof(LocationPanelLightTime));
}
}
}
public int LocationPanelDarkTimeMinutes
{
get => _locationPanelDarkTime;
set
{
if (_locationPanelDarkTime != value)
{
_locationPanelDarkTime = value;
NotifyPropertyChanged();
NotifyPropertyChanged(nameof(LocationPanelDarkTime));
}
}
}
public TimeSpan LocationPanelLightTime => TimeSpan.FromMinutes(_locationPanelLightTime);
public TimeSpan LocationPanelDarkTime => TimeSpan.FromMinutes(_locationPanelDarkTime);
public HotkeySettings ToggleThemeActivationShortcut
{
get => ModuleSettings.Properties.ToggleThemeHotkey.Value;

View File

@@ -652,6 +652,54 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
}
}
public int RecordFormatIndex
{
get
{
if (_zoomItSettings.Properties.RecordFormat.Value == "GIF")
{
return 0;
}
if (_zoomItSettings.Properties.RecordFormat.Value == "MP4")
{
return 1;
}
return 0;
}
set
{
int format = 0;
if (_zoomItSettings.Properties.RecordFormat.Value == "GIF")
{
format = 0;
}
if (_zoomItSettings.Properties.RecordFormat.Value == "MP4")
{
format = 1;
}
if (format != value)
{
_zoomItSettings.Properties.RecordFormat.Value = value == 0 ? "GIF" : "MP4";
OnPropertyChanged(nameof(RecordFormatIndex));
NotifySettingsChanged();
// Reload settings to get the new format's scaling value
var reloadedSettings = global::PowerToys.ZoomItSettingsInterop.ZoomItSettings.LoadSettingsJson();
var reloaded = JsonSerializer.Deserialize<ZoomItSettings>(reloadedSettings, _serializerOptions);
if (reloaded != null && reloaded.Properties != null)
{
_zoomItSettings.Properties.RecordScaling.Value = reloaded.Properties.RecordScaling.Value;
OnPropertyChanged(nameof(RecordScalingIndex));
}
}
}
}
public bool RecordCaptureAudio
{
get => _zoomItSettings.Properties.CaptureAudio.Value;

View File

@@ -92,7 +92,6 @@ void ReportGPOValues(const std::filesystem::path &tmpDir)
report << "getAllowedAdvancedPasteAzureAIInferenceValue: " << gpo_rule_configured_to_string(powertoys_gpo::getAllowedAdvancedPasteAzureAIInferenceValue()) << std::endl;
report << "getAllowedAdvancedPasteMistralValue: " << gpo_rule_configured_to_string(powertoys_gpo::getAllowedAdvancedPasteMistralValue()) << std::endl;
report << "getAllowedAdvancedPasteGoogleValue: " << gpo_rule_configured_to_string(powertoys_gpo::getAllowedAdvancedPasteGoogleValue()) << std::endl;
report << "getAllowedAdvancedPasteAnthropicValue: " << gpo_rule_configured_to_string(powertoys_gpo::getAllowedAdvancedPasteAnthropicValue()) << std::endl;
report << "getAllowedAdvancedPasteOllamaValue: " << gpo_rule_configured_to_string(powertoys_gpo::getAllowedAdvancedPasteOllamaValue()) << std::endl;
report << "getAllowedAdvancedPasteFoundryLocalValue: " << gpo_rule_configured_to_string(powertoys_gpo::getAllowedAdvancedPasteFoundryLocalValue()) << std::endl;
report << "getConfiguredMwbClipboardSharingEnabledValue: " << gpo_rule_configured_to_string(powertoys_gpo::getConfiguredMwbClipboardSharingEnabledValue()) << std::endl;

View File

@@ -46,6 +46,7 @@
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
<LanguageStandard>stdcpplatest</LanguageStandard>
</ClCompile>
<Link>
@@ -63,6 +64,7 @@
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<LanguageStandard>stdcpplatest</LanguageStandard>
</ClCompile>
<Link>

View File

@@ -47,6 +47,7 @@
<Optimization>Disabled</Optimization>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
<LanguageStandard>stdcpplatest</LanguageStandard>
</ClCompile>
<Link>
@@ -63,6 +64,7 @@
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<LanguageStandard>stdcpplatest</LanguageStandard>
</ClCompile>
<Link>