Compare commits
23 Commits
dev/vanzue
...
shawn/fixA
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
729c6b1965 | ||
|
|
b41ed2feb1 | ||
|
|
1b742ef817 | ||
|
|
2a40e1ce4d | ||
|
|
b015d6a778 | ||
|
|
3bfa0a0cf8 | ||
|
|
1940357004 | ||
|
|
5884375e9d | ||
|
|
a0a2f493c5 | ||
|
|
5e3e0660e7 | ||
|
|
29688cea0e | ||
|
|
a0f33c8af1 | ||
|
|
e1edcc13b7 | ||
|
|
756feb9f8c | ||
|
|
2fff688c6f | ||
|
|
24d7ae54ce | ||
|
|
a271a2f8af | ||
|
|
a33c484c93 | ||
|
|
f20c3b832b | ||
|
|
9c2884ab41 | ||
|
|
076828f592 | ||
|
|
301d504db1 | ||
|
|
e4a8488a2a |
10
.github/actions/spell-check/expect.txt
vendored
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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" />
|
||||
|
||||
46
README.md
@@ -7,19 +7,23 @@
|
||||
<h1 align="center">
|
||||
<span>Microsoft PowerToys</span>
|
||||
</h1>
|
||||
|
||||
<p align="center">
|
||||
<span align="center">Microsoft PowerToys is a collection of utilities that help you customize Windows and streamline everyday tasks.</span>
|
||||
</p>
|
||||
<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/>
|
||||
Microsoft PowerToys is a collection of utilities that help you customize Windows and streamline everyday tasks.
|
||||
<br/><br/>
|
||||
|
||||
## 🔨 Utilities
|
||||
|
||||
PowerToys includes over 25 utilities to help you customize and optimize your Windows experience:
|
||||
|
||||
| | | |
|
||||
|---|---|---|
|
||||
@@ -37,20 +41,13 @@ Microsoft PowerToys is a collection of utilities that help you customize Windows
|
||||
|
||||
## 📋 Installation
|
||||
|
||||
For detailed installation instructions, visit the [installation docs](https://learn.microsoft.com/windows/powertoys/install).
|
||||
|
||||
Before you begin, make sure your device meets the system requirements:
|
||||
|
||||
> [!NOTE]
|
||||
> - Windows 11 or Windows 10 version 2004 (20H1 / build 19041) or newer
|
||||
> - 64-bit processor: x64 or ARM64
|
||||
> - Latest stable version of [Microsoft Edge WebView2 Runtime](https://go.microsoft.com/fwlink/p/?LinkId=2124703) is installed via the bootstrapper during setup
|
||||
|
||||
Choose one of the installation methods below:
|
||||
For detailed installation instructions and system requirements, visit the [installation docs](https://learn.microsoft.com/windows/powertoys/install).
|
||||
|
||||
But to get started quickly, choose one of the installation methods below:
|
||||
<br/><br/>
|
||||
<details open>
|
||||
<summary>Download .exe from GitHub</summary>
|
||||
|
||||
<summary><strong>Download .exe from GitHub</strong></summary>
|
||||
<br/>
|
||||
Go to the [PowerToys GitHub releases][github-release-link], click Assets to reveal the downloads, and choose the installer that matches your architecture and install scope. For most devices, that's the x64 per-user installer.
|
||||
|
||||
<!-- items that need to be updated release to release -->
|
||||
@@ -67,11 +64,11 @@ Go to the [PowerToys GitHub releases][github-release-link], click Assets to reve
|
||||
| Per user - ARM64 | [PowerToysUserSetup-0.95.1-arm64.exe][ptUserArm64] |
|
||||
| Machine wide - x64 | [PowerToysSetup-0.95.1-x64.exe][ptMachineX64] |
|
||||
| Machine wide - ARM64 | [PowerToysSetup-0.95.1-arm64.exe][ptMachineArm64] |
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Microsoft Store</summary>
|
||||
<summary><strong>Microsoft Store</strong></summary>
|
||||
<br/>
|
||||
You can easily install PowerToys from the Microsoft Store:
|
||||
<p>
|
||||
<a style="text-decoration:none" href="https://aka.ms/getPowertoys">
|
||||
@@ -82,10 +79,9 @@ You can easily install PowerToys from the Microsoft Store:
|
||||
</p>
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>WinGet</summary>
|
||||
|
||||
<summary><strong>WinGet</strong></summary>
|
||||
<br/>
|
||||
Download PowerToys from [WinGet][winget-link]. Updating PowerToys via winget will respect the current PowerToys installation scope. To install PowerToys, run the following command from the command line / PowerShell:
|
||||
|
||||
*User scope installer [default]*
|
||||
@@ -100,8 +96,8 @@ winget install --scope machine Microsoft.PowerToys -s winget
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Other methods</summary>
|
||||
|
||||
<summary><strong>Other methods</strong></summary>
|
||||
<br/>
|
||||
There are [community driven install methods](./doc/unofficialInstallMethods.md) such as Chocolatey and Scoop. If these are your preferred install solutions, you can find the install instructions there.
|
||||
</details>
|
||||
|
||||
|
||||
BIN
doc/images/icons/CursorWrap.png
Normal file
|
After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 21 KiB |
BIN
doc/images/icons/MouseJump.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 22 KiB |
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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>
|
||||
@@ -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());
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -163,28 +163,143 @@ namespace AdvancedPaste.Settings
|
||||
return false;
|
||||
}
|
||||
|
||||
if (settings.Properties.IsAIEnabled || !LegacyOpenAIKeyExists())
|
||||
var properties = settings.Properties;
|
||||
var configuration = properties.PasteAIConfiguration;
|
||||
|
||||
if (configuration is null)
|
||||
{
|
||||
configuration = new PasteAIConfiguration();
|
||||
properties.PasteAIConfiguration = configuration;
|
||||
}
|
||||
|
||||
bool hasLegacyProviders = configuration.LegacyProviderConfigurations is { Count: > 0 };
|
||||
bool legacyAdvancedAIConsumed = properties.TryConsumeLegacyAdvancedAIEnabled(out var advancedFlag);
|
||||
bool legacyAdvancedAIEnabled = legacyAdvancedAIConsumed && advancedFlag;
|
||||
PasswordCredential legacyCredential = TryGetLegacyOpenAICredential();
|
||||
|
||||
if (!hasLegacyProviders && legacyCredential is null && !legacyAdvancedAIConsumed)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
settings.Properties.IsAIEnabled = true;
|
||||
return true;
|
||||
bool configurationUpdated = false;
|
||||
|
||||
if (hasLegacyProviders)
|
||||
{
|
||||
configurationUpdated |= AdvancedPasteMigrationHelper.MigrateLegacyProviderConfigurations(configuration);
|
||||
}
|
||||
|
||||
PasteAIProviderDefinition openAIProvider = null;
|
||||
if (legacyCredential is not null || hasLegacyProviders || legacyAdvancedAIConsumed)
|
||||
{
|
||||
var ensureResult = AdvancedPasteMigrationHelper.EnsureOpenAIProvider(configuration);
|
||||
openAIProvider = ensureResult.Provider;
|
||||
configurationUpdated |= ensureResult.Updated;
|
||||
}
|
||||
|
||||
if (legacyAdvancedAIConsumed && openAIProvider is not null && openAIProvider.EnableAdvancedAI != legacyAdvancedAIEnabled)
|
||||
{
|
||||
openAIProvider.EnableAdvancedAI = legacyAdvancedAIEnabled;
|
||||
configurationUpdated = true;
|
||||
}
|
||||
|
||||
if (legacyCredential is not null && openAIProvider is not null)
|
||||
{
|
||||
StoreMigratedOpenAICredential(openAIProvider.Id, openAIProvider.ServiceType, legacyCredential.Password);
|
||||
RemoveLegacyOpenAICredential();
|
||||
}
|
||||
|
||||
bool enabledUpdated = false;
|
||||
if (!properties.IsAIEnabled && legacyCredential is not null)
|
||||
{
|
||||
properties.IsAIEnabled = true;
|
||||
enabledUpdated = true;
|
||||
}
|
||||
|
||||
return configurationUpdated || enabledUpdated || legacyAdvancedAIConsumed;
|
||||
}
|
||||
|
||||
private static bool LegacyOpenAIKeyExists()
|
||||
private static PasswordCredential TryGetLegacyOpenAICredential()
|
||||
{
|
||||
try
|
||||
{
|
||||
PasswordVault vault = new();
|
||||
return vault.Retrieve("https://platform.openai.com/api-keys", "PowerToys_AdvancedPaste_OpenAIKey") is not null;
|
||||
var credential = vault.Retrieve("https://platform.openai.com/api-keys", "PowerToys_AdvancedPaste_OpenAIKey");
|
||||
credential?.RetrievePassword();
|
||||
return credential;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static void RemoveLegacyOpenAICredential()
|
||||
{
|
||||
try
|
||||
{
|
||||
PasswordVault vault = new();
|
||||
TryRemoveCredential(vault, "https://platform.openai.com/api-keys", "PowerToys_AdvancedPaste_OpenAIKey");
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private static void StoreMigratedOpenAICredential(string providerId, string serviceType, string password)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(password))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var serviceKind = serviceType.ToAIServiceType();
|
||||
if (serviceKind != AIServiceType.OpenAI)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string resource = "https://platform.openai.com/api-keys";
|
||||
string username = $"PowerToys_AdvancedPaste_PasteAI_openai_{NormalizeProviderIdentifier(providerId)}";
|
||||
|
||||
PasswordVault vault = new();
|
||||
TryRemoveCredential(vault, resource, username);
|
||||
|
||||
PasswordCredential credential = new(resource, username, password);
|
||||
vault.Add(credential);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError("Failed to migrate legacy OpenAI credential", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static void TryRemoveCredential(PasswordVault vault, string credentialResource, string credentialUserName)
|
||||
{
|
||||
try
|
||||
{
|
||||
PasswordCredential existingCred = vault.Retrieve(credentialResource, credentialUserName);
|
||||
vault.Remove(existingCred);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// Credential doesn't exist, which is fine
|
||||
}
|
||||
}
|
||||
|
||||
private static string NormalizeProviderIdentifier(string providerId)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(providerId))
|
||||
{
|
||||
return "default";
|
||||
}
|
||||
|
||||
var filtered = new string(providerId.Where(char.IsLetterOrDigit).ToArray());
|
||||
return string.IsNullOrWhiteSpace(filtered) ? "default" : filtered.ToLowerInvariant();
|
||||
}
|
||||
|
||||
public async Task SetActiveAIProviderAsync(string providerId)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(providerId))
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -181,8 +181,6 @@ namespace AdvancedPaste.Services.CustomActions
|
||||
{
|
||||
AIServiceType.Onnx => false,
|
||||
AIServiceType.Ollama => false,
|
||||
AIServiceType.Anthropic => false,
|
||||
AIServiceType.AmazonBedrock => false,
|
||||
_ => true,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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; }
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -81,7 +81,6 @@ public:
|
||||
void RemoveObserver(SettingsObserver& observer);
|
||||
|
||||
void LoadSettings();
|
||||
void ApplyThemeIfNecessary();
|
||||
|
||||
HANDLE GetSettingsChangedEvent() const;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -196,10 +196,10 @@ public:
|
||||
m_enabled = true;
|
||||
Trace::EnableCursorWrap(true);
|
||||
|
||||
if (m_autoActivate)
|
||||
{
|
||||
StartMouseHook();
|
||||
}
|
||||
// Always start the mouse hook when the module is enabled
|
||||
// This ensures cursor wrapping is active immediately after enabling
|
||||
StartMouseHook();
|
||||
Logger::info("CursorWrap enabled - mouse hook started");
|
||||
}
|
||||
|
||||
// Disable the powertoy
|
||||
@@ -208,6 +208,7 @@ public:
|
||||
m_enabled = false;
|
||||
Trace::EnableCursorWrap(false);
|
||||
StopMouseHook();
|
||||
Logger::info("CursorWrap disabled - mouse hook stopped");
|
||||
}
|
||||
|
||||
// Returns if the powertoys is enabled
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
548
src/modules/ZoomIt/ZoomIt/GifRecordingSession.cpp
Normal 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,
|
||||
®ion);
|
||||
|
||||
// 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();
|
||||
}
|
||||
69
src/modules/ZoomIt/ZoomIt/GifRecordingSession.h
Normal 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;
|
||||
};
|
||||
@@ -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
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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) }
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
@@ -68,7 +68,6 @@ public sealed partial class MainWindow : WindowEx,
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
HideWindow();
|
||||
|
||||
_hwnd = new HWND(WinRT.Interop.WindowNative.GetWindowHandle(this).ToInt32());
|
||||
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
Before Width: | Height: | Size: 550 B After Width: | Height: | Size: 604 B |
|
Before Width: | Height: | Size: 525 B After Width: | Height: | Size: 565 B |
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -17,10 +17,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
FoundryLocal,
|
||||
Mistral,
|
||||
Google,
|
||||
HuggingFace,
|
||||
AzureAIInference,
|
||||
Ollama,
|
||||
Anthropic,
|
||||
AmazonBedrock,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -0,0 +1,205 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper methods for migrating legacy Advanced Paste settings to the updated schema.
|
||||
/// </summary>
|
||||
public static class AdvancedPasteMigrationHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Moves legacy provider configuration snapshots into the strongly-typed providers collection.
|
||||
/// </summary>
|
||||
/// <param name="configuration">The configuration instance to migrate.</param>
|
||||
/// <returns>True if the configuration was modified.</returns>
|
||||
public static bool MigrateLegacyProviderConfigurations(PasteAIConfiguration configuration)
|
||||
{
|
||||
if (configuration is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
configuration.Providers ??= new ObservableCollection<PasteAIProviderDefinition>();
|
||||
|
||||
bool configurationUpdated = false;
|
||||
|
||||
if (configuration.LegacyProviderConfigurations is { Count: > 0 })
|
||||
{
|
||||
foreach (var entry in configuration.LegacyProviderConfigurations)
|
||||
{
|
||||
var result = EnsureProvider(configuration, entry.Key, entry.Value);
|
||||
configurationUpdated |= result.Updated;
|
||||
}
|
||||
|
||||
configuration.LegacyProviderConfigurations = null;
|
||||
}
|
||||
|
||||
configurationUpdated |= EnsureActiveProviderIsValid(configuration);
|
||||
|
||||
return configurationUpdated;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures an OpenAI provider exists in the configuration, creating one if necessary.
|
||||
/// </summary>
|
||||
/// <param name="configuration">The configuration instance.</param>
|
||||
/// <returns>The ensured provider and a flag indicating whether changes were made.</returns>
|
||||
public static (PasteAIProviderDefinition Provider, bool Updated) EnsureOpenAIProvider(PasteAIConfiguration configuration)
|
||||
{
|
||||
return EnsureProvider(configuration, AIServiceType.OpenAI.ToConfigurationString(), null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures a provider for the supplied service type exists, optionally applying a legacy snapshot.
|
||||
/// </summary>
|
||||
/// <param name="configuration">The configuration instance.</param>
|
||||
/// <param name="serviceTypeKey">The persisted service type key.</param>
|
||||
/// <param name="snapshot">An optional snapshot containing legacy values.</param>
|
||||
/// <returns>The ensured provider and whether the configuration was updated.</returns>
|
||||
public static (PasteAIProviderDefinition Provider, bool Updated) EnsureProvider(PasteAIConfiguration configuration, string serviceTypeKey, AIProviderConfigurationSnapshot snapshot)
|
||||
{
|
||||
if (configuration is null)
|
||||
{
|
||||
return (null, false);
|
||||
}
|
||||
|
||||
configuration.Providers ??= new ObservableCollection<PasteAIProviderDefinition>();
|
||||
|
||||
var normalizedServiceType = NormalizeServiceType(serviceTypeKey);
|
||||
var existingProvider = configuration.Providers.FirstOrDefault(provider => string.Equals(provider.ServiceType, normalizedServiceType, StringComparison.OrdinalIgnoreCase));
|
||||
bool configurationUpdated = false;
|
||||
|
||||
if (existingProvider is null)
|
||||
{
|
||||
existingProvider = CreateProvider(normalizedServiceType, snapshot);
|
||||
configuration.Providers.Add(existingProvider);
|
||||
configurationUpdated = true;
|
||||
}
|
||||
else if (snapshot is not null)
|
||||
{
|
||||
configurationUpdated |= ApplySnapshot(existingProvider, snapshot);
|
||||
}
|
||||
|
||||
configurationUpdated |= EnsureActiveProviderIsValid(configuration, existingProvider);
|
||||
|
||||
return (existingProvider, configurationUpdated);
|
||||
}
|
||||
|
||||
private static string NormalizeServiceType(string serviceTypeKey)
|
||||
{
|
||||
var serviceType = serviceTypeKey.ToAIServiceType();
|
||||
return serviceType.ToConfigurationString();
|
||||
}
|
||||
|
||||
private static PasteAIProviderDefinition CreateProvider(string serviceTypeKey, AIProviderConfigurationSnapshot snapshot)
|
||||
{
|
||||
var serviceType = serviceTypeKey.ToAIServiceType();
|
||||
var metadata = AIServiceTypeRegistry.GetMetadata(serviceType);
|
||||
var provider = new PasteAIProviderDefinition
|
||||
{
|
||||
ServiceType = serviceTypeKey,
|
||||
ModelName = !string.IsNullOrWhiteSpace(snapshot?.ModelName) ? snapshot.ModelName : PasteAIProviderDefaults.GetDefaultModelName(serviceType),
|
||||
EndpointUrl = snapshot?.EndpointUrl ?? string.Empty,
|
||||
ApiVersion = snapshot?.ApiVersion ?? string.Empty,
|
||||
DeploymentName = snapshot?.DeploymentName ?? string.Empty,
|
||||
ModelPath = snapshot?.ModelPath ?? string.Empty,
|
||||
SystemPrompt = snapshot?.SystemPrompt ?? string.Empty,
|
||||
ModerationEnabled = snapshot?.ModerationEnabled ?? true,
|
||||
IsLocalModel = metadata.IsLocalModel,
|
||||
};
|
||||
|
||||
return provider;
|
||||
}
|
||||
|
||||
private static bool ApplySnapshot(PasteAIProviderDefinition provider, AIProviderConfigurationSnapshot snapshot)
|
||||
{
|
||||
bool updated = false;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(snapshot.ModelName) && !string.Equals(provider.ModelName, snapshot.ModelName, StringComparison.Ordinal))
|
||||
{
|
||||
provider.ModelName = snapshot.ModelName;
|
||||
updated = true;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(snapshot.EndpointUrl) && !string.Equals(provider.EndpointUrl, snapshot.EndpointUrl, StringComparison.Ordinal))
|
||||
{
|
||||
provider.EndpointUrl = snapshot.EndpointUrl;
|
||||
updated = true;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(snapshot.ApiVersion) && !string.Equals(provider.ApiVersion, snapshot.ApiVersion, StringComparison.Ordinal))
|
||||
{
|
||||
provider.ApiVersion = snapshot.ApiVersion;
|
||||
updated = true;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(snapshot.DeploymentName) && !string.Equals(provider.DeploymentName, snapshot.DeploymentName, StringComparison.Ordinal))
|
||||
{
|
||||
provider.DeploymentName = snapshot.DeploymentName;
|
||||
updated = true;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(snapshot.ModelPath) && !string.Equals(provider.ModelPath, snapshot.ModelPath, StringComparison.Ordinal))
|
||||
{
|
||||
provider.ModelPath = snapshot.ModelPath;
|
||||
updated = true;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(snapshot.SystemPrompt) && !string.Equals(provider.SystemPrompt, snapshot.SystemPrompt, StringComparison.Ordinal))
|
||||
{
|
||||
provider.SystemPrompt = snapshot.SystemPrompt;
|
||||
updated = true;
|
||||
}
|
||||
|
||||
if (provider.ModerationEnabled != snapshot.ModerationEnabled)
|
||||
{
|
||||
provider.ModerationEnabled = snapshot.ModerationEnabled;
|
||||
updated = true;
|
||||
}
|
||||
|
||||
return updated;
|
||||
}
|
||||
|
||||
private static bool EnsureActiveProviderIsValid(PasteAIConfiguration configuration, PasteAIProviderDefinition preferredProvider = null)
|
||||
{
|
||||
if (configuration?.Providers is null || configuration.Providers.Count == 0)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(configuration?.ActiveProviderId))
|
||||
{
|
||||
configuration.ActiveProviderId = string.Empty;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool updated = false;
|
||||
|
||||
var activeProvider = configuration.Providers.FirstOrDefault(provider => string.Equals(provider.Id, configuration.ActiveProviderId, StringComparison.OrdinalIgnoreCase));
|
||||
if (activeProvider is null)
|
||||
{
|
||||
activeProvider = preferredProvider ?? configuration.Providers.First();
|
||||
configuration.ActiveProviderId = activeProvider.Id;
|
||||
updated = true;
|
||||
}
|
||||
|
||||
foreach (var provider in configuration.Providers)
|
||||
{
|
||||
bool shouldBeActive = string.Equals(provider.Id, configuration.ActiveProviderId, StringComparison.OrdinalIgnoreCase);
|
||||
if (provider.IsActive != shouldBeActive)
|
||||
{
|
||||
provider.IsActive = shouldBeActive;
|
||||
updated = true;
|
||||
}
|
||||
}
|
||||
|
||||
return updated;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -52,10 +52,68 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
|
||||
_extensionData.Remove("IsOpenAIEnabled");
|
||||
}
|
||||
|
||||
if (_extensionData != null && _extensionData.TryGetValue("IsAdvancedAIEnabled", out var legacyAdvancedElement))
|
||||
{
|
||||
bool? legacyValue = legacyAdvancedElement.ValueKind switch
|
||||
{
|
||||
JsonValueKind.True => true,
|
||||
JsonValueKind.False => false,
|
||||
JsonValueKind.Object when legacyAdvancedElement.TryGetProperty("value", out var advancedValueElement) => advancedValueElement.ValueKind switch
|
||||
{
|
||||
JsonValueKind.True => true,
|
||||
JsonValueKind.False => false,
|
||||
_ => null,
|
||||
},
|
||||
_ => null,
|
||||
};
|
||||
|
||||
if (legacyValue.HasValue)
|
||||
{
|
||||
LegacyAdvancedAIEnabled = legacyValue.Value;
|
||||
}
|
||||
|
||||
_extensionData.Remove("IsAdvancedAIEnabled");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Dictionary<string, JsonElement> _extensionData;
|
||||
private bool? _legacyAdvancedAIEnabled;
|
||||
|
||||
[JsonPropertyName("IsAdvancedAIEnabled")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public BoolProperty LegacyAdvancedAIEnabledProperty
|
||||
{
|
||||
get => null;
|
||||
set
|
||||
{
|
||||
if (value is not null)
|
||||
{
|
||||
LegacyAdvancedAIEnabled = value.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public bool? LegacyAdvancedAIEnabled
|
||||
{
|
||||
get => _legacyAdvancedAIEnabled;
|
||||
private set => _legacyAdvancedAIEnabled = value;
|
||||
}
|
||||
|
||||
public bool TryConsumeLegacyAdvancedAIEnabled(out bool value)
|
||||
{
|
||||
if (_legacyAdvancedAIEnabled is bool flag)
|
||||
{
|
||||
value = flag;
|
||||
_legacyAdvancedAIEnabled = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
[JsonConverter(typeof(BoolPropertyJsonConverter))]
|
||||
public bool ShowCustomPreview { get; set; }
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
// 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.
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides default values for Paste AI provider definitions.
|
||||
/// </summary>
|
||||
public static class PasteAIProviderDefaults
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the default model name for a given AI service type.
|
||||
/// </summary>
|
||||
public static string GetDefaultModelName(AIServiceType serviceType)
|
||||
{
|
||||
return serviceType switch
|
||||
{
|
||||
AIServiceType.OpenAI => "gpt-4o",
|
||||
AIServiceType.AzureOpenAI => "gpt-4o",
|
||||
AIServiceType.Mistral => "mistral-large-latest",
|
||||
AIServiceType.Google => "gemini-1.5-pro",
|
||||
AIServiceType.AzureAIInference => "gpt-4o-mini",
|
||||
AIServiceType.Ollama => "llama3",
|
||||
_ => string.Empty,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
|
||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 1.7 KiB |
@@ -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 |
|
Before Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.3 KiB |
33
src/settings-ui/Settings.UI/Converters/StringToDouble.cs
Normal 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";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -42,8 +42,9 @@
|
||||
HorizontalAlignment="Center" />
|
||||
<TextBlock
|
||||
x:Name="LoadingStatusTextBlock"
|
||||
x:Uid="AdvancedPaste_FL_LoadingStatus"
|
||||
HorizontalAlignment="Center"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Text="Loading Foundry Local status..."
|
||||
TextAlignment="Center"
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
@@ -56,7 +57,6 @@
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<StackPanel
|
||||
x:Name="NoModelsPanel"
|
||||
Grid.Row="0"
|
||||
@@ -64,24 +64,28 @@
|
||||
HorizontalAlignment="Center"
|
||||
Orientation="Vertical"
|
||||
Spacing="4">
|
||||
<FontIcon FontSize="24" Glyph="" />
|
||||
<FontIcon
|
||||
AutomationProperties.AccessibilityView="Raw"
|
||||
FontSize="24"
|
||||
Glyph="" />
|
||||
<TextBlock
|
||||
x:Uid="AdvancedPaste_FL_NoModelsDownloaded."
|
||||
HorizontalAlignment="Center"
|
||||
Style="{StaticResource BodyStrongTextBlockStyle}"
|
||||
Text="No models downloaded"
|
||||
TextAlignment="Center" />
|
||||
<TextBlock
|
||||
x:Uid="AdvancedPaste_FL_RunFoundryLocalText.Text"
|
||||
HorizontalAlignment="Center"
|
||||
FontSize="12"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Text="Run Foundry Local to download or add a local model below."
|
||||
TextAlignment="Center"
|
||||
TextWrapping="Wrap" />
|
||||
<Button
|
||||
x:Name="LaunchFoundryModelListButton"
|
||||
x:Uid="AdvancedPaste_FL_OpenFoundryModelList"
|
||||
Margin="0,8,0,0"
|
||||
HorizontalAlignment="Center"
|
||||
Click="LaunchFoundryModelListButton_Click"
|
||||
Content="Open Foundry model list"
|
||||
Style="{StaticResource AccentButtonStyle}" />
|
||||
</StackPanel>
|
||||
|
||||
@@ -101,10 +105,10 @@
|
||||
SelectionChanged="CachedModelsComboBox_SelectionChanged">
|
||||
<ComboBox.Header>
|
||||
<TextBlock>
|
||||
<Run Text="Foundry Local model" /><LineBreak /><Run
|
||||
<Run x:Uid="AdvancedPaste_FL_LocalModel" /><LineBreak /><Run
|
||||
x:Uid="AdvancedPaste_FL_UseCLIToDownloadModels"
|
||||
FontSize="12"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Text="Use the Foundry Local CLI to download models that run locally on-device. They'll appear here." />
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
|
||||
</TextBlock>
|
||||
</ComboBox.Header>
|
||||
</ComboBox>
|
||||
@@ -114,9 +118,11 @@
|
||||
MinHeight="32"
|
||||
VerticalAlignment="Bottom"
|
||||
Click="RefreshModelsButton_Click"
|
||||
Style="{StaticResource SubtleButtonStyle}"
|
||||
ToolTipService.ToolTip="Refresh model list">
|
||||
Style="{StaticResource SubtleButtonStyle}">
|
||||
<FontIcon FontSize="16" Glyph="" />
|
||||
<ToolTipService.ToolTip>
|
||||
<TextBlock x:Uid="AdvancedPaste_FL_RefreshModelList" />
|
||||
</ToolTipService.ToolTip>
|
||||
</Button>
|
||||
</Grid>
|
||||
<StackPanel
|
||||
@@ -146,24 +152,28 @@
|
||||
Spacing="8">
|
||||
<Image Width="36" Source="ms-appx:///Assets/Settings/Icons/Models/FoundryLocal.svg" />
|
||||
<TextBlock
|
||||
x:Uid="AdvancedPaste_FL_FLNotavailableYet"
|
||||
HorizontalAlignment="Center"
|
||||
FontWeight="SemiBold"
|
||||
Text="Foundry Local is not available on this device yet."
|
||||
TextAlignment="Center"
|
||||
TextWrapping="Wrap" />
|
||||
<TextBlock
|
||||
x:Uid="AdvancedPaste_FL_StartService"
|
||||
HorizontalAlignment="Center"
|
||||
FontSize="12"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
IsTextSelectionEnabled="True"
|
||||
TextAlignment="Center"
|
||||
TextWrapping="Wrap">
|
||||
<Run Text="Start the Foundry Local service before returning to PowerToys." />
|
||||
</TextBlock>
|
||||
<HyperlinkButton Content="Follow the Foundry Local CLI guide" NavigateUri="https://learn.microsoft.com/en-us/azure/ai-foundry/foundry-local/get-started" />
|
||||
TextWrapping="Wrap" />
|
||||
<HyperlinkButton
|
||||
x:Uid="AdvancedPaste_FL_CLIGuide"
|
||||
HorizontalAlignment="Center"
|
||||
NavigateUri="https://learn.microsoft.com/azure/ai-foundry/foundry-local/get-started" />
|
||||
<TextBlock
|
||||
x:Uid="FoundryLocal_RestartRequiredNote"
|
||||
HorizontalAlignment="Center"
|
||||
FontSize="12"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Text="Note: After installing the Foundry Local CLI, restart PowerToys to use it."
|
||||
TextAlignment="Center"
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
|
||||
@@ -103,10 +103,7 @@
|
||||
</StackPanel>
|
||||
</tkcontrols:SettingsExpander.Description>
|
||||
<tkcontrols:SettingsExpander.ItemsHeader>
|
||||
<tkcontrols:SettingsCard
|
||||
Description="Add online or local models"
|
||||
Header="Model providers"
|
||||
Style="{StaticResource DefaultSettingsExpanderItemStyle}">
|
||||
<tkcontrols:SettingsCard x:Uid="AdvancedPaste_ModelProviders" Style="{StaticResource DefaultSettingsExpanderItemStyle}">
|
||||
<Button Content="Add model" Style="{StaticResource AccentButtonStyle}">
|
||||
<Button.Flyout>
|
||||
<MenuFlyout x:Name="AddProviderMenuFlyout" Opening="AddProviderMenuFlyout_Opening" />
|
||||
@@ -130,16 +127,16 @@
|
||||
<Button.Flyout>
|
||||
<MenuFlyout>
|
||||
<MenuFlyoutItem
|
||||
x:Uid="AdvancedPaste_Edit"
|
||||
Click="EditPasteAIProviderButton_Click"
|
||||
Icon="{ui:FontIcon Glyph=}"
|
||||
Tag="{x:Bind}"
|
||||
Text="Edit" />
|
||||
Tag="{x:Bind}" />
|
||||
<MenuFlyoutSeparator />
|
||||
<MenuFlyoutItem
|
||||
x:Uid="AdvancedPaste_Remove"
|
||||
Click="RemovePasteAIProviderButton_Click"
|
||||
Icon="{ui:FontIcon Glyph=}"
|
||||
Tag="{x:Bind}"
|
||||
Text="Remove" />
|
||||
Tag="{x:Bind}" />
|
||||
</MenuFlyout>
|
||||
</Button.Flyout>
|
||||
</Button>
|
||||
@@ -496,46 +493,44 @@
|
||||
Spacing="16">
|
||||
<TextBox
|
||||
x:Name="PasteAIModelNameTextBox"
|
||||
x:Uid="AdvancedPaste_ModelName"
|
||||
MinWidth="200"
|
||||
HorizontalAlignment="Stretch"
|
||||
Header="Model name"
|
||||
PlaceholderText="gpt-4"
|
||||
PlaceholderText="gpt-4o"
|
||||
Text="{x:Bind ViewModel.PasteAIProviderDraft.ModelName, Mode=TwoWay}" />
|
||||
<TextBox
|
||||
x:Name="PasteAIEndpointUrlTextBox"
|
||||
x:Uid="AdvancedPaste_EndpointURL"
|
||||
MinWidth="200"
|
||||
HorizontalAlignment="Stretch"
|
||||
Header="Endpoint URL"
|
||||
PlaceholderText="https://your-resource.openai.azure.com/"
|
||||
Text="{x:Bind ViewModel.PasteAIProviderDraft.EndpointUrl, Mode=TwoWay}" />
|
||||
|
||||
<PasswordBox
|
||||
x:Name="PasteAIApiKeyPasswordBox"
|
||||
MinWidth="200"
|
||||
Header="API key"
|
||||
PlaceholderText="Enter API Key" />
|
||||
x:Uid="AdvancedPaste_APIKey"
|
||||
MinWidth="200" />
|
||||
<TextBox
|
||||
x:Name="PasteAIApiVersionTextBox"
|
||||
x:Uid="AdvancedPaste_APIVersion"
|
||||
MinWidth="200"
|
||||
HorizontalAlignment="Stretch"
|
||||
Header="API version"
|
||||
PlaceholderText="2024-10-01"
|
||||
Text="{x:Bind ViewModel.PasteAIProviderDraft.ApiVersion, Mode=TwoWay}"
|
||||
Visibility="Collapsed" />
|
||||
<TextBox
|
||||
x:Name="PasteAIDeploymentNameTextBox"
|
||||
x:Uid="AdvancedPaste_DeploymentName"
|
||||
MinWidth="200"
|
||||
Header="Deployment name"
|
||||
PlaceholderText="gpt-4"
|
||||
PlaceholderText="gpt-4o"
|
||||
Text="{x:Bind ViewModel.PasteAIProviderDraft.DeploymentName, Mode=TwoWay}" />
|
||||
<TextBox
|
||||
x:Name="PasteAISystemPromptTextBox"
|
||||
x:Uid="AdvancedPaste_SystemPrompt"
|
||||
MinWidth="200"
|
||||
MinHeight="76"
|
||||
HorizontalAlignment="Stretch"
|
||||
AcceptsReturn="True"
|
||||
Header="System prompt"
|
||||
PlaceholderText="You are tasked with reformatting user's clipboard data. Use the user's instructions, and the content of their clipboard below to edit their clipboard content as they have requested it. Do not output anything else besides the reformatted clipboard content."
|
||||
Text="{x:Bind ViewModel.PasteAIProviderDraft.SystemPrompt, Mode=TwoWay}"
|
||||
TextWrapping="Wrap" />
|
||||
<Grid
|
||||
@@ -575,13 +570,10 @@
|
||||
IsOn="{x:Bind ViewModel.PasteAIProviderDraft.EnableAdvancedAI, Mode=TwoWay}"
|
||||
Toggled="PasteAIEnableAdvancedAICheckBox_Toggled"
|
||||
Visibility="Collapsed">
|
||||
<ToolTipService.ToolTip>
|
||||
<TextBlock Text="" TextWrapping="Wrap" />
|
||||
</ToolTipService.ToolTip>
|
||||
<ToggleSwitch.Header>
|
||||
<TextBlock>
|
||||
<Run Text="Enable Advanced AI" /> <LineBreak />
|
||||
<Run Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="Use built-in functions to handle complex tasks. Token consumption may increase." />
|
||||
<Run x:Uid="AdvancedPaste_EnableAdvancedAI" /> <LineBreak />
|
||||
<Run x:Uid="AdvancedPaste_EnableAdvancedAIDescription" Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
|
||||
</TextBlock>
|
||||
</ToggleSwitch.Header>
|
||||
</ToggleSwitch>
|
||||
|
||||
@@ -36,6 +36,8 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
|
||||
private const string AdvancedAISystemPrompt = "You are an agent who is tasked with helping users paste their clipboard data. You have functions available to help you with this task. Call function when necessary to help user finish the transformation task. You never need to ask permission, always try to do as the user asks. The user will only input one message and will not be available for further questions, so try your best. The user will put in a request to format their clipboard data and you will fulfill it. Do not output anything else besides the reformatted clipboard content.";
|
||||
private const string SimpleAISystemPrompt = "You are tasked with reformatting user's clipboard data. Use the user's instructions, and the content of their clipboard below to edit their clipboard content as they have requested it. Do not output anything else besides the reformatted clipboard content.";
|
||||
private static readonly string AdvancedAISystemPromptNormalized = AdvancedAISystemPrompt.Trim();
|
||||
private static readonly string SimpleAISystemPromptNormalized = SimpleAISystemPrompt.Trim();
|
||||
|
||||
private AdvancedPasteViewModel ViewModel { get; set; }
|
||||
|
||||
@@ -317,7 +319,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;
|
||||
@@ -805,6 +806,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
return;
|
||||
}
|
||||
|
||||
NormalizeSystemPrompt(draft);
|
||||
string serviceType = draft.ServiceType ?? "OpenAI";
|
||||
string apiKey = PasteAIApiKeyPasswordBox.Password;
|
||||
string trimmedApiKey = apiKey?.Trim() ?? string.Empty;
|
||||
@@ -835,22 +837,8 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
return;
|
||||
}
|
||||
|
||||
bool isEmptyOrDefault = string.IsNullOrWhiteSpace(draft.SystemPrompt) ||
|
||||
draft.SystemPrompt.Trim() == AdvancedAISystemPrompt.Trim() ||
|
||||
draft.SystemPrompt.Trim() == SimpleAISystemPrompt.Trim();
|
||||
|
||||
if (isEmptyOrDefault)
|
||||
{
|
||||
if (!draft.EnableAdvancedAI)
|
||||
{
|
||||
// Now we'll switch
|
||||
draft.SystemPrompt = AdvancedAISystemPrompt;
|
||||
}
|
||||
else
|
||||
{
|
||||
draft.SystemPrompt = SimpleAISystemPrompt;
|
||||
}
|
||||
}
|
||||
NormalizeSystemPrompt(draft);
|
||||
UpdateSystemPromptPlaceholder();
|
||||
}
|
||||
|
||||
private static bool RequiresApiKeyForService(string serviceType)
|
||||
@@ -874,7 +862,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/",
|
||||
};
|
||||
@@ -952,15 +939,47 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
|
||||
private Visibility GetServicePrivacyVisibility(string serviceType) => HasServicePrivacyLink(serviceType) ? Visibility.Visible : Visibility.Collapsed;
|
||||
|
||||
private void UpdateSystemPromptPlaceholder()
|
||||
private static bool IsPlaceholderSystemPrompt(string prompt)
|
||||
{
|
||||
var draft = ViewModel?.PasteAIProviderDraft;
|
||||
if (draft is null || PasteAISystemPromptTextBox is null)
|
||||
if (string.IsNullOrWhiteSpace(prompt))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
string trimmedPrompt = prompt.Trim();
|
||||
return string.Equals(trimmedPrompt, AdvancedAISystemPromptNormalized, StringComparison.Ordinal)
|
||||
|| string.Equals(trimmedPrompt, SimpleAISystemPromptNormalized, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
private static void NormalizeSystemPrompt(PasteAIProviderDefinition draft)
|
||||
{
|
||||
if (draft is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
PasteAISystemPromptTextBox.PlaceholderText = draft.EnableAdvancedAI
|
||||
if (IsPlaceholderSystemPrompt(draft.SystemPrompt))
|
||||
{
|
||||
draft.SystemPrompt = string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateSystemPromptPlaceholder()
|
||||
{
|
||||
var draft = ViewModel?.PasteAIProviderDraft;
|
||||
if (draft is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
NormalizeSystemPrompt(draft);
|
||||
if (PasteAISystemPromptTextBox is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bool useAdvancedPlaceholder = PasteAIEnableAdvancedAICheckBox?.IsOn ?? draft.EnableAdvancedAI;
|
||||
PasteAISystemPromptTextBox.PlaceholderText = useAdvancedPlaceholder
|
||||
? AdvancedAISystemPrompt
|
||||
: SimpleAISystemPrompt;
|
||||
}
|
||||
|
||||
@@ -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=,
|
||||
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="" />
|
||||
<TextBlock x:Uid="LightSwitch_GetCurrentLocation" />
|
||||
<FontIcon FontSize="16" Glyph="" />
|
||||
<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="">
|
||||
<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="">
|
||||
<FontIcon FontSize="20" Glyph="">
|
||||
<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="">
|
||||
<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>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -644,14 +644,11 @@ Please review the placeholder content that represents the final terms and usage
|
||||
<data name="AdvancedPaste_EnablePasteAIModerationToggle.Header" xml:space="preserve">
|
||||
<value>Enable OpenAI content moderation</value>
|
||||
</data>
|
||||
<data name="AdvancedPaste_EnableAdvancedAI_SettingsCard.Header" xml:space="preserve">
|
||||
<value>Enable Advanced AI</value>
|
||||
</data>
|
||||
<data name="AdvancedPaste_EnableAdvancedAI_SettingsCard.Description" xml:space="preserve">
|
||||
<data name="AdvancedPaste_EnableAdvancedAIDescription.Text" xml:space="preserve">
|
||||
<value>Use built-in functions to handle complex tasks. Token consumption may increase.</value>
|
||||
</data>
|
||||
<data name="AdvancedPaste_Clipboard_History_Enabled_SettingsCard.Header" xml:space="preserve">
|
||||
<value>Access Clipboard History</value>
|
||||
<value>Show what's currently on your Clipboard and access your Clipboard history</value>
|
||||
</data>
|
||||
<data name="AdvancedPaste_Clipboard_History_Enabled_SettingsCard.Description" xml:space="preserve">
|
||||
<value>Clipboard History shows a list of previously copied items.</value>
|
||||
@@ -708,15 +705,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 +714,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 +2248,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 +2674,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 +2684,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 +2693,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>
|
||||
@@ -4084,11 +4055,8 @@ Activate by holding the key for the character you want to add an accent to, then
|
||||
<data name="AdvancedPaste_ShowCustomPreviewSettingsCard.Description" xml:space="preserve">
|
||||
<value>Preview the output of AI formats and Image to text before pasting</value>
|
||||
</data>
|
||||
<data name="AdvancedPaste_EnableAdvancedAI.Header" xml:space="preserve">
|
||||
<value>Advanced AI</value>
|
||||
</data>
|
||||
<data name="AdvancedPaste_EnableAdvancedAI.Description" xml:space="preserve">
|
||||
<value>Supports advanced workflows by chaining transformations and working with files and images. May use additional API credits.</value>
|
||||
<data name="AdvancedPaste_EnableAdvancedAI.Text" xml:space="preserve">
|
||||
<value>Enable Advanced AI</value>
|
||||
</data>
|
||||
<data name="Oobe_AdvancedPaste.Description" xml:space="preserve">
|
||||
<value>Advanced Paste is a tool to put your clipboard content into any format you need, focused towards developer workflows. It can paste as plain text, markdown, or json directly with the UX or with a direct keystroke invoke. These are fully locally executed. In addition, it has an AI powered option that is 100% opt-in and requires an Open AI key. Note: this will replace the formatted text in your clipboard with the selected format.</value>
|
||||
@@ -4630,7 +4598,7 @@ Activate by holding the key for the character you want to add an accent to, then
|
||||
<value>If you do not have credits you will see an 'API key quota exceeded' error</value>
|
||||
</data>
|
||||
<data name="AdvancedPaste_CloseAfterLosingFocus.Content" xml:space="preserve">
|
||||
<value>Automatically close the Advanced Paste window after it loses focus</value>
|
||||
<value>Automatically close the window after it loses focus</value>
|
||||
<comment>Advanced Paste is a product name, do not loc</comment>
|
||||
</data>
|
||||
<data name="GPO_CommandNotFound_ForceDisabled.Title" xml:space="preserve">
|
||||
@@ -5046,6 +5014,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 +5451,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 +5465,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 +5559,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 +5670,77 @@ 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>
|
||||
<data name="AdvancedPaste_FL_OpenFoundryModelList.Content" xml:space="preserve">
|
||||
<value>Open Foundry Local model list</value>
|
||||
<comment>Do not localize "Foundry Local", it's a product name</comment>
|
||||
</data>
|
||||
<data name="AdvancedPaste_FL_RunFoundryLocalText.Text" xml:space="preserve">
|
||||
<value>Run Foundry Local to download or add a local model</value>
|
||||
<comment>Do not localize "Foundry Local", it's a product name</comment>
|
||||
</data>
|
||||
<data name="AdvancedPaste_FL_NoModelsDownloaded.Text" xml:space="preserve">
|
||||
<value>No models downloaded</value>
|
||||
</data>
|
||||
<data name="AdvancedPaste_FL_LoadingStatus.Text" xml:space="preserve">
|
||||
<value>Loading Foundry Local status..</value>
|
||||
<comment>Do not localize "Foundry Local", it's a product name</comment>
|
||||
</data>
|
||||
<data name="AdvancedPaste_FL_LocalModel.Text" xml:space="preserve">
|
||||
<value>Foundry Local model</value>
|
||||
<comment>Do not localize "Foundry Local", it's a product name</comment>
|
||||
</data>
|
||||
<data name="AdvancedPaste_FL_UseCLIToDownloadModels.Text" xml:space="preserve">
|
||||
<value>Use the Foundry Local CLI to download models that run locally on-device. They'll appear here.</value>
|
||||
<comment>Do not localize "Foundry Local", it's a product name</comment>
|
||||
</data>
|
||||
<data name="AdvancedPaste_FL_RefreshModelList.Text" xml:space="preserve">
|
||||
<value>Refresh model list</value>
|
||||
</data>
|
||||
<data name="AdvancedPaste_FL_FLNotavailableYet.Text" xml:space="preserve">
|
||||
<value>Foundry Local is not available on this device yet.</value>
|
||||
<comment>Do not localize "Foundry Local", it's a product name</comment>
|
||||
</data>
|
||||
<data name="AdvancedPaste_FL_StartService.Text" xml:space="preserve">
|
||||
<value>Start the Foundry Local service before returning to PowerToys.</value>
|
||||
</data>
|
||||
<data name="AdvancedPaste_FL_CLIGuide.Content" xml:space="preserve">
|
||||
<value>Follow the Foundry Local CLI guide</value>
|
||||
<comment>Do not localize "Foundry Local", it's a product name</comment>
|
||||
</data>
|
||||
<data name="AdvancedPaste_ModelProviders.Header" xml:space="preserve">
|
||||
<value>Model providers</value>
|
||||
</data>
|
||||
<data name="AdvancedPaste_ModelProviders.Description" xml:space="preserve">
|
||||
<value>Add online or local models</value>
|
||||
</data>
|
||||
<data name="AdvancedPaste_Edit.Text" xml:space="preserve">
|
||||
<value>Edit</value>
|
||||
</data>
|
||||
<data name="AdvancedPaste_Remove.Text" xml:space="preserve">
|
||||
<value>Remove</value>
|
||||
</data>
|
||||
<data name="AdvancedPaste_ModelName.Header" xml:space="preserve">
|
||||
<value>Model name</value>
|
||||
</data>
|
||||
<data name="AdvancedPaste_EndpointURL.Header" xml:space="preserve">
|
||||
<value>Endpoint URL</value>
|
||||
</data>
|
||||
<data name="AdvancedPaste_APIKey.Header" xml:space="preserve">
|
||||
<value>API key</value>
|
||||
</data>
|
||||
<data name="AdvancedPaste_APIKey.PlaceholderText" xml:space="preserve">
|
||||
<value>Enter API key</value>
|
||||
</data>
|
||||
<data name="AdvancedPaste_APIVersion.Header" xml:space="preserve">
|
||||
<value>API version</value>
|
||||
</data>
|
||||
<data name="AdvancedPaste_DeploymentName.Header" xml:space="preserve">
|
||||
<value>Deployment name</value>
|
||||
</data>
|
||||
<data name="AdvancedPaste_SystemPrompt.Header" xml:space="preserve">
|
||||
<value>System prompt</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -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,
|
||||
@@ -164,19 +172,90 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
|
||||
private void MigrateLegacyAIEnablement()
|
||||
{
|
||||
if (_advancedPasteSettings.Properties.IsAIEnabled || IsOnlineAIModelsDisallowedByGPO)
|
||||
var properties = _advancedPasteSettings?.Properties;
|
||||
if (properties is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!LegacyOpenAIKeyExists())
|
||||
bool legacyAdvancedAIConsumed = properties.TryConsumeLegacyAdvancedAIEnabled(out var advancedFlag);
|
||||
bool legacyAdvancedAIEnabled = legacyAdvancedAIConsumed && advancedFlag;
|
||||
|
||||
if (IsOnlineAIModelsDisallowedByGPO)
|
||||
{
|
||||
if (legacyAdvancedAIConsumed)
|
||||
{
|
||||
SaveAndNotifySettings();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var configuration = properties.PasteAIConfiguration;
|
||||
if (configuration is null)
|
||||
{
|
||||
configuration = new PasteAIConfiguration();
|
||||
properties.PasteAIConfiguration = configuration;
|
||||
}
|
||||
|
||||
bool hasLegacyProviders = configuration.LegacyProviderConfigurations is { Count: > 0 };
|
||||
PasswordCredential legacyCredential = TryGetLegacyOpenAICredential();
|
||||
|
||||
if (!hasLegacyProviders && legacyCredential is null && !legacyAdvancedAIConsumed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_advancedPasteSettings.Properties.IsAIEnabled = true;
|
||||
SaveAndNotifySettings();
|
||||
OnPropertyChanged(nameof(IsAIEnabled));
|
||||
bool configurationUpdated = false;
|
||||
|
||||
if (hasLegacyProviders)
|
||||
{
|
||||
configurationUpdated |= AdvancedPasteMigrationHelper.MigrateLegacyProviderConfigurations(configuration);
|
||||
}
|
||||
|
||||
PasteAIProviderDefinition openAIProvider = null;
|
||||
if (legacyCredential is not null || hasLegacyProviders || legacyAdvancedAIConsumed)
|
||||
{
|
||||
var ensureResult = AdvancedPasteMigrationHelper.EnsureOpenAIProvider(configuration);
|
||||
openAIProvider = ensureResult.Provider;
|
||||
configurationUpdated |= ensureResult.Updated;
|
||||
}
|
||||
|
||||
if (legacyAdvancedAIConsumed && openAIProvider is not null && openAIProvider.EnableAdvancedAI != legacyAdvancedAIEnabled)
|
||||
{
|
||||
openAIProvider.EnableAdvancedAI = legacyAdvancedAIEnabled;
|
||||
configurationUpdated = true;
|
||||
}
|
||||
|
||||
if (legacyCredential is not null && openAIProvider is not null)
|
||||
{
|
||||
SavePasteAIApiKey(openAIProvider.Id, openAIProvider.ServiceType, legacyCredential.Password);
|
||||
RemoveLegacyOpenAICredential();
|
||||
}
|
||||
|
||||
bool enabledChanged = false;
|
||||
if (!properties.IsAIEnabled && legacyCredential is not null)
|
||||
{
|
||||
properties.IsAIEnabled = true;
|
||||
enabledChanged = true;
|
||||
}
|
||||
|
||||
bool shouldPersist = configurationUpdated || enabledChanged || legacyAdvancedAIConsumed;
|
||||
|
||||
if (shouldPersist)
|
||||
{
|
||||
SaveAndNotifySettings();
|
||||
|
||||
if (configurationUpdated)
|
||||
{
|
||||
OnPropertyChanged(nameof(PasteAIConfiguration));
|
||||
}
|
||||
|
||||
if (enabledChanged)
|
||||
{
|
||||
OnPropertyChanged(nameof(IsAIEnabled));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsEnabled
|
||||
@@ -221,34 +300,30 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
|
||||
public bool IsAIEnabled => _advancedPasteSettings.Properties.IsAIEnabled && !IsOnlineAIModelsDisallowedByGPO;
|
||||
|
||||
private bool LegacyOpenAIKeyExists()
|
||||
private PasswordCredential TryGetLegacyOpenAICredential()
|
||||
{
|
||||
try
|
||||
{
|
||||
PasswordVault vault = new();
|
||||
|
||||
// return vault.Retrieve("https://platform.openai.com/api-keys", "PowerToys_AdvancedPaste_OpenAIKey") is not null;
|
||||
var legacyOpenAIKey = vault.Retrieve("https://platform.openai.com/api-keys", "PowerToys_AdvancedPaste_OpenAIKey");
|
||||
if (legacyOpenAIKey != null)
|
||||
{
|
||||
string credentialResource = GetAICredentialResource("OpenAI");
|
||||
var targetProvider = PasteAIConfiguration?.ActiveProvider ?? PasteAIConfiguration?.Providers?.FirstOrDefault();
|
||||
string providerId = targetProvider?.Id ?? string.Empty;
|
||||
string serviceType = targetProvider?.ServiceType ?? "OpenAI";
|
||||
string credentialUserName = GetPasteAICredentialUserName(providerId, serviceType);
|
||||
PasswordCredential cred = new(credentialResource, credentialUserName, legacyOpenAIKey.Password);
|
||||
vault.Add(cred);
|
||||
|
||||
// delete old key
|
||||
TryRemoveCredential(vault, "https://platform.openai.com/api-keys", "PowerToys_AdvancedPaste_OpenAIKey");
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
var credential = vault.Retrieve("https://platform.openai.com/api-keys", "PowerToys_AdvancedPaste_OpenAIKey");
|
||||
credential?.RetrievePassword();
|
||||
return credential;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveLegacyOpenAICredential()
|
||||
{
|
||||
try
|
||||
{
|
||||
PasswordVault vault = new();
|
||||
TryRemoveCredential(vault, "https://platform.openai.com/api-keys", "PowerToys_AdvancedPaste_OpenAIKey");
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -511,7 +586,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
var provider = new PasteAIProviderDefinition
|
||||
{
|
||||
ServiceType = persistedServiceType,
|
||||
ModelName = GetDefaultModelName(normalizedServiceType),
|
||||
ModelName = PasteAIProviderDefaults.GetDefaultModelName(normalizedServiceType),
|
||||
EndpointUrl = string.Empty,
|
||||
ApiVersion = string.Empty,
|
||||
DeploymentName = string.Empty,
|
||||
@@ -551,22 +626,6 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
return serviceTypeKind;
|
||||
}
|
||||
|
||||
private static string GetDefaultModelName(AIServiceType serviceType)
|
||||
{
|
||||
return serviceType switch
|
||||
{
|
||||
AIServiceType.OpenAI => "gpt-4",
|
||||
AIServiceType.AzureOpenAI => "gpt-4",
|
||||
AIServiceType.Mistral => "mistral-large-latest",
|
||||
AIServiceType.Google => "gemini-1.5-pro",
|
||||
AIServiceType.AzureAIInference => "gpt-4o-mini",
|
||||
AIServiceType.Ollama => "llama3",
|
||||
AIServiceType.Anthropic => "claude-3-5-sonnet",
|
||||
AIServiceType.AmazonBedrock => "anthropic.claude-3-haiku",
|
||||
_ => string.Empty,
|
||||
};
|
||||
}
|
||||
|
||||
public bool IsServiceTypeAllowedByGPO(AIServiceType serviceType)
|
||||
{
|
||||
var metadata = AIServiceTypeRegistry.GetMetadata(serviceType);
|
||||
@@ -589,7 +648,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 +902,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 +1055,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();
|
||||
}
|
||||
@@ -1350,7 +1405,16 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
}
|
||||
|
||||
pasteConfig.Providers ??= new ObservableCollection<PasteAIProviderDefinition>();
|
||||
|
||||
bool configurationUpdated = AdvancedPasteMigrationHelper.MigrateLegacyProviderConfigurations(pasteConfig);
|
||||
|
||||
SubscribeToPasteAIProviders(pasteConfig);
|
||||
|
||||
if (configurationUpdated)
|
||||
{
|
||||
SaveAndNotifySettings();
|
||||
OnPropertyChanged(nameof(PasteAIConfiguration));
|
||||
}
|
||||
}
|
||||
|
||||
private static string RetrieveCredentialValue(string credentialResource, string credentialUserName)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1000,6 +1000,13 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
GeneralSettingsConfig.Enabled.CursorWrap = value;
|
||||
OnPropertyChanged(nameof(IsCursorWrapEnabled));
|
||||
|
||||
// Auto-enable the AutoActivate setting when CursorWrap is enabled
|
||||
// This ensures cursor wrapping is active immediately after enabling
|
||||
if (value && !_cursorWrapAutoActivate)
|
||||
{
|
||||
CursorWrapAutoActivate = true;
|
||||
}
|
||||
|
||||
OutGoingGeneralSettings outgoing = new OutGoingGeneralSettings(GeneralSettingsConfig);
|
||||
SendConfigMSG(outgoing.ToString());
|
||||
|
||||
|
||||
@@ -24,6 +24,9 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
{
|
||||
public class ZoomItViewModel : Observable
|
||||
{
|
||||
private const string FormatGif = "GIF";
|
||||
private const string FormatMp4 = "MP4";
|
||||
|
||||
private ISettingsUtils SettingsUtils { get; set; }
|
||||
|
||||
private GeneralSettings GeneralSettingsConfig { get; set; }
|
||||
@@ -652,6 +655,54 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
public int RecordFormatIndex
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_zoomItSettings.Properties.RecordFormat.Value == FormatGif)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (_zoomItSettings.Properties.RecordFormat.Value == FormatMp4)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
int format = 0;
|
||||
if (_zoomItSettings.Properties.RecordFormat.Value == FormatGif)
|
||||
{
|
||||
format = 0;
|
||||
}
|
||||
|
||||
if (_zoomItSettings.Properties.RecordFormat.Value == FormatMp4)
|
||||
{
|
||||
format = 1;
|
||||
}
|
||||
|
||||
if (format != value)
|
||||
{
|
||||
_zoomItSettings.Properties.RecordFormat.Value = value == 0 ? FormatGif : FormatMp4;
|
||||
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;
|
||||
|
||||