diff --git a/.github/actions/spell-check/excludes.txt b/.github/actions/spell-check/excludes.txt
index c6f1225788..551c248923 100644
--- a/.github/actions/spell-check/excludes.txt
+++ b/.github/actions/spell-check/excludes.txt
@@ -105,6 +105,7 @@
^src/common/notifications/BackgroundActivatorDLL/cpp\.hint$
^src/common/sysinternals/Eula/
^src/modules/cmdpal/doc/initial-sdk-spec/list-elements-mock-002\.pdn$
+^src/modules/cmdpal/ext/SamplePagesExtension/Pages/SampleMarkdownImagesPage\.cs$
^src/modules/colorPicker/ColorPickerUI/Shaders/GridShader\.cso$
^src/modules/launcher/Plugins/Microsoft\.PowerToys\.Run\.Plugin\.TimeDate/Properties/
^src/modules/MouseUtils/MouseJumpUI/MainForm\.resx$
diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt
index b3966292e9..d113aa3519 100644
--- a/.github/actions/spell-check/expect.txt
+++ b/.github/actions/spell-check/expect.txt
@@ -47,7 +47,6 @@ Allmodule
ALLOWUNDO
ALLVIEW
ALPHATYPE
-amazonbedrock
AModifier
amr
ANDSCANS
@@ -66,6 +65,7 @@ APIIs
Apm
APPBARDATA
APPEXECLINK
+appext
APPLICATIONFRAMEHOST
appmanifest
APPMODEL
@@ -101,7 +101,6 @@ ATX
ATRIOX
aumid
authenticode
-Authenticode
AUTOBUDDY
AUTOCHECKBOX
AUTOHIDE
@@ -188,6 +187,7 @@ CAPTUREBLT
CAPTURECHANGED
CARETBLINKING
CAtl
+CBN
cch
CCHDEVICENAME
CCHFORMNAME
@@ -316,7 +316,6 @@ CURSORINFO
cursorpos
CURSORSHOWING
CURSORWRAP
-CursorWrap
customaction
CUSTOMACTIONTEST
CUSTOMFORMATPLACEHOLDER
@@ -416,6 +415,9 @@ DNLEN
DONOTROUND
DONTVALIDATEPATH
dotnet
+downsampled
+downsampling
+Downsampled
downscale
DPICHANGED
DPIs
@@ -432,7 +434,6 @@ DSTINVERT
DString
DSVG
dto
-DTo
DUMMYUNIONNAME
dutil
DVASPECT
@@ -466,7 +467,6 @@ EDITKEYBOARD
EDITSHORTCUTS
EDITTEXT
EFile
-ekus
eku
emojis
ENABLEDELAYEDEXPANSION
@@ -602,6 +602,7 @@ getfilesiginforedist
geolocator
GETHOTKEY
GETICON
+GETLBTEXT
GETMINMAXINFO
GETNONCLIENTMETRICS
GETPROPERTYSTOREFLAGS
@@ -609,6 +610,7 @@ GETSCREENSAVERRUNNING
GETSECKEY
GETSTICKYKEYS
GETTEXTLENGTH
+GIFs
gitmodules
GHND
GMEM
@@ -619,6 +621,7 @@ GPOCA
gpp
gpu
gradians
+grctlext
Gridcustomlayout
GSM
gtm
@@ -692,7 +695,6 @@ hmonitor
homies
homljgmgpmcbpjbnjpfijnhipfkiclkd
HOOKPROC
-huggingface
HORZRES
HORZSIZE
Hostbackdropbrush
@@ -1152,7 +1154,6 @@ NONCLIENTMETRICSW
NONELEVATED
nonspace
nonstd
-nullrefs
NOOWNERZORDER
NOPARENTNOTIFY
NOPREFIX
@@ -1192,8 +1193,8 @@ ntfs
NTSTATUS
NTSYSAPI
NULLCURSOR
-nullref
nullonfailure
+nullref
numberbox
nwc
ocr
diff --git a/.github/actions/spell-check/patterns.txt b/.github/actions/spell-check/patterns.txt
index cb303a10ad..181d728e84 100644
--- a/.github/actions/spell-check/patterns.txt
+++ b/.github/actions/spell-check/patterns.txt
@@ -253,7 +253,7 @@ _SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING
# hit-count: 1 file-count: 1
# Amazon
-\bamazon\.com/[-\w]+/(?:dp/[0-9A-Z]+|)[^"'\s]+
+\bamazon\.com/[-\w]+/(?:dp/[0-9A-Z]+|)
# hit-count: 3 file-count: 3
# imgur
diff --git a/.pipelines/v2/release.yml b/.pipelines/v2/release.yml
index e8cc7d5ed8..71f80f574b 100644
--- a/.pipelines/v2/release.yml
+++ b/.pipelines/v2/release.yml
@@ -52,8 +52,6 @@ extends:
name: SHINE-INT-S
${{ if eq(parameters.useVSPreview, true) }}:
demands: ImageOverride -equals SHINE-VS17-Preview
- ${{ else }}:
- image: SHINE-VS17-Latest
os: windows
sdl:
tsa:
@@ -75,7 +73,6 @@ extends:
name: SHINE-INT-L
demands:
# Our INT agents have a large disk mounted at P:\
- - WorkFolder -equals P:\_work
- ${{ if eq(parameters.useVSPreview, true) }}:
- ImageOverride -equals SHINE-VS17-Preview
os: windows
@@ -126,7 +123,6 @@ extends:
parameters:
pool:
name: SHINE-INT-L
- image: SHINE-VS17-Latest
os: windows
official: true
codeSign: true
diff --git a/.pipelines/v2/templates/job-build-project.yml b/.pipelines/v2/templates/job-build-project.yml
index 5fba82854a..ed64676016 100644
--- a/.pipelines/v2/templates/job-build-project.yml
+++ b/.pipelines/v2/templates/job-build-project.yml
@@ -111,6 +111,7 @@ jobs:
${{ else }}:
OutputBuildPlatform: ${{ platform }}
variables:
+ NUGET_PACKAGES: 'C:\NuGetPackages' # Some of our build steps cache these here... and it was apparently part of the global environment
MakeAppxPath: 'C:\Program Files (x86)\Windows Kits\10\bin\10.0.26100.0\x86\MakeAppx.exe'
# Azure DevOps abhors a vacuum
# If these are blank, expansion will fail later on... which will result in direct substitution of the variable *names*
@@ -139,6 +140,10 @@ jobs:
- output: pipelineArtifact
artifactName: $(JobOutputArtifactName)
targetPath: $(Build.ArtifactStagingDirectory)
+ - output: pipelineArtifact
+ artifactName: $(JobOutputArtifactName)-failure-$(System.JobAttempt)
+ targetPath: $(LogOutputDirectory)
+ condition: or(failed(), canceled())
steps:
- checkout: self
clean: true
@@ -395,7 +400,7 @@ jobs:
### HACK: On ARM64 builds, building an app with Windows App SDK copies the x64 WebView2 dll instead of the ARM64 one. This task makes sure the right dll is used.
- task: CopyFiles@2
displayName: HACK Copy core WebView2 ARM64 dll to output directory
- condition: eq(variables['BuildPlatform'],'arm64')
+ condition: and(succeeded(), eq(variables['BuildPlatform'], 'arm64'))
inputs:
contents: packages/Microsoft.Web.WebView2.1.0.2903.40/runtimes/win-ARM64/native_uap/Microsoft.Web.WebView2.Core.dll
targetFolder: $(Build.SourcesDirectory)/ARM64/Release/WinUI3Apps/
@@ -434,11 +439,11 @@ jobs:
inputs:
testResultsFormat: VSTest
testResultsFiles: '**/*.trx'
- condition: ne(variables['BuildPlatform'],'arm64')
+ condition: and(succeeded(), ne(variables['BuildPlatform'], 'arm64'))
# Native dlls
- task: VSTest@2
- condition: ne(variables['BuildPlatform'],'arm64') # No arm64 agents to run the tests.
+ condition: and(succeeded(), ne(variables['BuildPlatform'], 'arm64')) # No arm64 agents to run the tests.
displayName: 'Native Tests'
inputs:
platform: '$(BuildPlatform)'
diff --git a/Cpp.Build.props b/Cpp.Build.props
index 245bd76b24..7df112096b 100644
--- a/Cpp.Build.props
+++ b/Cpp.Build.props
@@ -1,56 +1,6 @@
-
-
- $(Configuration)
-
-
-
-
-
-
-
-
-
-
- MultiThreadedDebug
-
-
-
- %(IgnoreSpecificDefaultLibraries);libucrtd.lib
- %(AdditionalOptions) /defaultlib:ucrtd.lib
-
-
-
-
-
- MultiThreaded
-
-
-
- %(IgnoreSpecificDefaultLibraries);libucrt.lib
- %(AdditionalOptions) /defaultlib:ucrt.lib
-
-
-
-
-
-
- MultiThreadedDebugDLL
-
-
-
-
- MultiThreadedDLL
-
-
@@ -127,6 +77,7 @@
_DEBUG;%(PreprocessorDefinitions)
Disabled
+ MultiThreadedDebug
true
@@ -136,6 +87,7 @@
NDEBUG;%(PreprocessorDefinitions)
MaxSpeed
+ MultiThreaded
true
true
diff --git a/Directory.Packages.props b/Directory.Packages.props
index 15b5289fcf..2409821f4b 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -51,10 +51,8 @@
-
-
diff --git a/README.md b/README.md
index e737281523..067d6d6f50 100644
--- a/README.md
+++ b/README.md
@@ -10,11 +10,11 @@
diff --git a/doc/images/icons/CursorWrap.png b/doc/images/icons/CursorWrap.png
new file mode 100644
index 0000000000..20db84fc9a
Binary files /dev/null and b/doc/images/icons/CursorWrap.png differ
diff --git a/doc/images/icons/Find My Mouse.png b/doc/images/icons/Find My Mouse.png
index 71dd994569..82fbe59800 100644
Binary files a/doc/images/icons/Find My Mouse.png and b/doc/images/icons/Find My Mouse.png differ
diff --git a/doc/images/icons/Mouse Highlighter.png b/doc/images/icons/Mouse Highlighter.png
index b06843d941..0feb5cc15a 100644
Binary files a/doc/images/icons/Mouse Highlighter.png and b/doc/images/icons/Mouse Highlighter.png differ
diff --git a/doc/images/icons/MouseJump.png b/doc/images/icons/MouseJump.png
new file mode 100644
index 0000000000..2fbe450ac2
Binary files /dev/null and b/doc/images/icons/MouseJump.png differ
diff --git a/doc/images/icons/MouseWithoutBorders.png b/doc/images/icons/MouseWithoutBorders.png
index a29adf7d11..ee66893cbd 100644
Binary files a/doc/images/icons/MouseWithoutBorders.png and b/doc/images/icons/MouseWithoutBorders.png differ
diff --git a/installer/PowerToysSetupCustomActionsVNext/PowerToysSetupCustomActionsVNext.vcxproj b/installer/PowerToysSetupCustomActionsVNext/PowerToysSetupCustomActionsVNext.vcxproj
index 9934dc2867..87a0333169 100644
--- a/installer/PowerToysSetupCustomActionsVNext/PowerToysSetupCustomActionsVNext.vcxproj
+++ b/installer/PowerToysSetupCustomActionsVNext/PowerToysSetupCustomActionsVNext.vcxproj
@@ -110,6 +110,7 @@
Disabled
_DEBUG;_WINDOWS;_USRDLL;CUSTOMACTIONTEST_EXPORTS;%(PreprocessorDefinitions)
EnableFastChecks
+ MultiThreadedDebug
true
@@ -122,6 +123,7 @@
MaxSpeed
true
NDEBUG;_WINDOWS;_USRDLL;CUSTOMACTIONTEST_EXPORTS;%(PreprocessorDefinitions)
+ MultiThreaded
true
diff --git a/installer/PowerToysSetupVNext/Directory.Build.props b/installer/PowerToysSetupVNext/Directory.Build.props
index 69a63832d1..505e3cf844 100644
--- a/installer/PowerToysSetupVNext/Directory.Build.props
+++ b/installer/PowerToysSetupVNext/Directory.Build.props
@@ -1,5 +1,4 @@
-
@@ -9,4 +8,4 @@
$(BaseIntermediateOutputPath)
-
\ No newline at end of file
+
diff --git a/installer/PowerToysSetupVNext/Product.wxs b/installer/PowerToysSetupVNext/Product.wxs
index 556fddc7f4..3e812beb2e 100644
--- a/installer/PowerToysSetupVNext/Product.wxs
+++ b/installer/PowerToysSetupVNext/Product.wxs
@@ -120,8 +120,8 @@
-
-
+
+
diff --git a/installer/PowerToysSetupVNext/SilentFilesInUseBA/SilentFilesInUseBAFunction.vcxproj b/installer/PowerToysSetupVNext/SilentFilesInUseBA/SilentFilesInUseBAFunction.vcxproj
index d45e32f87c..3972c1b0f7 100644
--- a/installer/PowerToysSetupVNext/SilentFilesInUseBA/SilentFilesInUseBAFunction.vcxproj
+++ b/installer/PowerToysSetupVNext/SilentFilesInUseBA/SilentFilesInUseBAFunction.vcxproj
@@ -1,7 +1,30 @@
+
+
+
+
+ Debug
+ ARM64
+
+
+ Release
+ ARM64
+
+
+ Debug
+ x64
+
+
+ Release
+ x64
+
+
+
{F8B9F842-F5C3-4A2D-8C85-7F8B9E2B4F1D}
+ DynamicLibrary
+ Unicode
SilentFilesInUseBAFunction
PowerToysSetupCustomActionsVNext
bafunctions.def
@@ -10,6 +33,7 @@
+
DynamicLibrary
true
@@ -41,10 +65,7 @@
-
- Use
- precomp.h
-
+
Create
precomp.h
@@ -71,5 +92,31 @@
+
+
+
+ _DEBUG;%(PreprocessorDefinitions)
+ Disabled
+ MultiThreadedDebug
+
+
+ true
+
+
+
+
+ NDEBUG;%(PreprocessorDefinitions)
+ MaxSpeed
+ MultiThreaded
+ true
+ true
+
+
+ true
+ true
+ true
+
+
+
diff --git a/installer/PowerToysSetupVNext/SilentFilesInUseBA/SilentFilesInUseBAFunctions.cpp b/installer/PowerToysSetupVNext/SilentFilesInUseBA/SilentFilesInUseBAFunctions.cpp
index ceccde5f0d..9b9e5d570f 100644
--- a/installer/PowerToysSetupVNext/SilentFilesInUseBA/SilentFilesInUseBAFunctions.cpp
+++ b/installer/PowerToysSetupVNext/SilentFilesInUseBA/SilentFilesInUseBAFunctions.cpp
@@ -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);
diff --git a/src/common/CalculatorEngineCommon/CalculatorEngineCommon.vcxproj b/src/common/CalculatorEngineCommon/CalculatorEngineCommon.vcxproj
index d22a6cbe84..22c79cc2ac 100644
--- a/src/common/CalculatorEngineCommon/CalculatorEngineCommon.vcxproj
+++ b/src/common/CalculatorEngineCommon/CalculatorEngineCommon.vcxproj
@@ -9,6 +9,12 @@
CalculatorEngineCommon
false
+
+
+ true
+ false
+
+
true
@@ -19,9 +25,11 @@
true
- false
+ true
true
Windows Store
+ false
+
@@ -140,5 +148,43 @@
-
+
+
+
+
+
+
+ MultiThreadedDebug
+ stdcpp17
+
+
+
+ %(IgnoreSpecificDefaultLibraries);libucrtd.lib
+ %(AdditionalOptions) /defaultlib:ucrtd.lib
+
+
+
+
+
+ MultiThreaded
+
+
+
+ %(IgnoreSpecificDefaultLibraries);libucrt.lib
+ %(AdditionalOptions) /defaultlib:ucrt.lib
+
+
+
\ No newline at end of file
diff --git a/src/common/GPOWrapper/GPOWrapper.cpp b/src/common/GPOWrapper/GPOWrapper.cpp
index 52c91a0795..2b256cd926 100644
--- a/src/common/GPOWrapper/GPOWrapper.cpp
+++ b/src/common/GPOWrapper/GPOWrapper.cpp
@@ -216,10 +216,6 @@ namespace winrt::PowerToys::GPOWrapper::implementation
{
return static_cast(powertoys_gpo::getAllowedAdvancedPasteGoogleValue());
}
- GpoRuleConfigured GPOWrapper::GetAllowedAdvancedPasteAnthropicValue()
- {
- return static_cast(powertoys_gpo::getAllowedAdvancedPasteAnthropicValue());
- }
GpoRuleConfigured GPOWrapper::GetAllowedAdvancedPasteOllamaValue()
{
return static_cast(powertoys_gpo::getAllowedAdvancedPasteOllamaValue());
diff --git a/src/common/GPOWrapper/GPOWrapper.h b/src/common/GPOWrapper/GPOWrapper.h
index f2f89c17ba..07b92e50ef 100644
--- a/src/common/GPOWrapper/GPOWrapper.h
+++ b/src/common/GPOWrapper/GPOWrapper.h
@@ -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();
diff --git a/src/common/GPOWrapper/GPOWrapper.idl b/src/common/GPOWrapper/GPOWrapper.idl
index ab6dbf0c29..06d035aa35 100644
--- a/src/common/GPOWrapper/GPOWrapper.idl
+++ b/src/common/GPOWrapper/GPOWrapper.idl
@@ -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();
diff --git a/src/common/interop/PowerToys.Interop.vcxproj b/src/common/interop/PowerToys.Interop.vcxproj
index 71bacadb8e..c20dd84227 100644
--- a/src/common/interop/PowerToys.Interop.vcxproj
+++ b/src/common/interop/PowerToys.Interop.vcxproj
@@ -63,12 +63,14 @@
+ MultiThreadedDebug
true
true
+ MultiThreaded
false
true
false
diff --git a/src/common/notifications/BackgroundActivator/BackgroundActivator.vcxproj b/src/common/notifications/BackgroundActivator/BackgroundActivator.vcxproj
index a2cea8a4fb..aece9626ac 100644
--- a/src/common/notifications/BackgroundActivator/BackgroundActivator.vcxproj
+++ b/src/common/notifications/BackgroundActivator/BackgroundActivator.vcxproj
@@ -46,6 +46,16 @@
notifications
+
+
+ MultiThreadedDebugDLL
+
+
+
+
+ MultiThreadedDLL
+
+
$(IntDir)pch.pch
diff --git a/src/common/utils/gpo.h b/src/common/utils/gpo.h
index ecf338d212..ab71d09d0b 100644
--- a/src/common/utils/gpo.h
+++ b/src/common/utils/gpo.h
@@ -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);
diff --git a/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPaste.csproj b/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPaste.csproj
index 6c843e18a5..6b475ddcea 100644
--- a/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPaste.csproj
+++ b/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPaste.csproj
@@ -59,10 +59,8 @@
-
-
diff --git a/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/App.xaml.cs b/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/App.xaml.cs
index 2737fb44c2..3fa940952e 100644
--- a/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/App.xaml.cs
+++ b/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/App.xaml.cs
@@ -112,11 +112,7 @@ namespace AdvancedPaste
/// Invoked when the application is launched.
///
/// Details about the launch request and process.
-#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)
diff --git a/src/modules/AdvancedPaste/AdvancedPaste/Helpers/UserSettings.cs b/src/modules/AdvancedPaste/AdvancedPaste/Helpers/UserSettings.cs
index b6b6c19734..cf93f10796 100644
--- a/src/modules/AdvancedPaste/AdvancedPaste/Helpers/UserSettings.cs
+++ b/src/modules/AdvancedPaste/AdvancedPaste/Helpers/UserSettings.cs
@@ -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))
diff --git a/src/modules/AdvancedPaste/AdvancedPaste/Services/AdvancedAIKernelService.cs b/src/modules/AdvancedPaste/AdvancedPaste/Services/AdvancedAIKernelService.cs
index b9566ed481..759d6ec57d 100644
--- a/src/modules/AdvancedPaste/AdvancedPaste/Services/AdvancedAIKernelService.cs
+++ b/src/modules/AdvancedPaste/AdvancedPaste/Services/AdvancedAIKernelService.cs
@@ -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,
};
}
diff --git a/src/modules/AdvancedPaste/AdvancedPaste/Services/CustomActions/CustomActionTransformService.cs b/src/modules/AdvancedPaste/AdvancedPaste/Services/CustomActions/CustomActionTransformService.cs
index 721a96070d..65d37bfdd8 100644
--- a/src/modules/AdvancedPaste/AdvancedPaste/Services/CustomActions/CustomActionTransformService.cs
+++ b/src/modules/AdvancedPaste/AdvancedPaste/Services/CustomActions/CustomActionTransformService.cs
@@ -181,8 +181,6 @@ namespace AdvancedPaste.Services.CustomActions
{
AIServiceType.Onnx => false,
AIServiceType.Ollama => false,
- AIServiceType.Anthropic => false,
- AIServiceType.AmazonBedrock => false,
_ => true,
};
}
diff --git a/src/modules/AdvancedPaste/AdvancedPaste/Services/CustomActions/SemanticKernelPasteProvider.cs b/src/modules/AdvancedPaste/AdvancedPaste/Services/CustomActions/SemanticKernelPasteProvider.cs
index 00517e96d8..819549b466 100644
--- a/src/modules/AdvancedPaste/AdvancedPaste/Services/CustomActions/SemanticKernelPasteProvider.cs
+++ b/src/modules/AdvancedPaste/AdvancedPaste/Services/CustomActions/SemanticKernelPasteProvider.cs
@@ -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,
};
}
diff --git a/src/modules/AdvancedPaste/AdvancedPaste/Services/EnhancedVaultCredentialsProvider.cs b/src/modules/AdvancedPaste/AdvancedPaste/Services/EnhancedVaultCredentialsProvider.cs
index 2542f7310e..648881fba0 100644
--- a/src/modules/AdvancedPaste/AdvancedPaste/Services/EnhancedVaultCredentialsProvider.cs
+++ b/src/modules/AdvancedPaste/AdvancedPaste/Services/EnhancedVaultCredentialsProvider.cs
@@ -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;
diff --git a/src/modules/AdvancedPaste/AdvancedPaste/Telemetry/AdvancedPasteEndpointUsageEvent.cs b/src/modules/AdvancedPaste/AdvancedPaste/Telemetry/AdvancedPasteEndpointUsageEvent.cs
index 04777eff79..7b179a7cd5 100644
--- a/src/modules/AdvancedPaste/AdvancedPaste/Telemetry/AdvancedPasteEndpointUsageEvent.cs
+++ b/src/modules/AdvancedPaste/AdvancedPaste/Telemetry/AdvancedPasteEndpointUsageEvent.cs
@@ -15,7 +15,7 @@ namespace AdvancedPaste.Telemetry;
public class AdvancedPasteEndpointUsageEvent : EventBase, IEvent
{
///
- /// Gets or sets the AI provider type (e.g., OpenAI, AzureOpenAI, Anthropic).
+ /// Gets or sets the AI provider type (e.g., OpenAI, AzureOpenAI, Google).
///
public string ProviderType { get; set; }
diff --git a/src/modules/AdvancedPaste/AdvancedPaste/ViewModels/OptionsViewModel.cs b/src/modules/AdvancedPaste/AdvancedPaste/ViewModels/OptionsViewModel.cs
index 0cda4fc6e5..467497b9a9 100644
--- a/src/modules/AdvancedPaste/AdvancedPaste/ViewModels/OptionsViewModel.cs
+++ b/src/modules/AdvancedPaste/AdvancedPaste/ViewModels/OptionsViewModel.cs
@@ -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
diff --git a/src/modules/CropAndLock/CropAndLock/CropAndLock.vcxproj b/src/modules/CropAndLock/CropAndLock/CropAndLock.vcxproj
index 37f75b6fa7..83d4b7eafb 100644
--- a/src/modules/CropAndLock/CropAndLock/CropAndLock.vcxproj
+++ b/src/modules/CropAndLock/CropAndLock/CropAndLock.vcxproj
@@ -82,6 +82,8 @@
Disabled
_DEBUG;%(PreprocessorDefinitions)
+ MultiThreadedDebug
+ MultiThreadedDebug
false
@@ -93,6 +95,8 @@
true
true
NDEBUG;%(PreprocessorDefinitions)
+ MultiThreaded
+ MultiThreaded
true
diff --git a/src/modules/FileLocksmith/FileLocksmithLibInterop/FileLocksmithLibInterop.vcxproj b/src/modules/FileLocksmith/FileLocksmithLibInterop/FileLocksmithLibInterop.vcxproj
index 2c66e70106..d7e1193876 100644
--- a/src/modules/FileLocksmith/FileLocksmithLibInterop/FileLocksmithLibInterop.vcxproj
+++ b/src/modules/FileLocksmith/FileLocksmithLibInterop/FileLocksmithLibInterop.vcxproj
@@ -39,6 +39,7 @@
_DEBUG;%(PreprocessorDefinitions)
+ MultiThreadedDebugDLL
true
true
@@ -48,6 +49,7 @@
NDEBUG;%(PreprocessorDefinitions)
true
true
+ MultiThreadedDLL
false
true
false
diff --git a/src/modules/LightSwitch/LightSwitchService/LightSwitchService.cpp b/src/modules/LightSwitch/LightSwitchService/LightSwitchService.cpp
index 503a98de29..74efeda933 100644
--- a/src/modules/LightSwitch/LightSwitchService/LightSwitchService.cpp
+++ b/src/modules/LightSwitch/LightSwitchService/LightSwitchService.cpp
@@ -11,19 +11,17 @@
#include
#include
#include
+#include "LightSwitchStateManager.h"
+#include
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;
}
diff --git a/src/modules/LightSwitch/LightSwitchService/LightSwitchService.vcxproj b/src/modules/LightSwitch/LightSwitchService/LightSwitchService.vcxproj
index 59044e64a0..83f06e3522 100644
--- a/src/modules/LightSwitch/LightSwitchService/LightSwitchService.vcxproj
+++ b/src/modules/LightSwitch/LightSwitchService/LightSwitchService.vcxproj
@@ -75,6 +75,7 @@
+
@@ -85,6 +86,8 @@
+
+
diff --git a/src/modules/LightSwitch/LightSwitchService/LightSwitchService.vcxproj.filters b/src/modules/LightSwitch/LightSwitchService/LightSwitchService.vcxproj.filters
index f5aa05afc3..795df99aba 100644
--- a/src/modules/LightSwitch/LightSwitchService/LightSwitchService.vcxproj.filters
+++ b/src/modules/LightSwitch/LightSwitchService/LightSwitchService.vcxproj.filters
@@ -33,6 +33,9 @@
Source Files
+
+ Source Files
+
@@ -53,6 +56,12 @@
Header Files
+
+ Header Files
+
+
+ Header Files
+
diff --git a/src/modules/LightSwitch/LightSwitchService/LightSwitchSettings.cpp b/src/modules/LightSwitch/LightSwitchService/LightSwitchSettings.cpp
index 8105b0ab3a..5221a197fe 100644
--- a/src/modules/LightSwitch/LightSwitchService/LightSwitchSettings.cpp
+++ b/src/modules/LightSwitch/LightSwitchService/LightSwitchSettings.cpp
@@ -2,10 +2,8 @@
#include
#include
#include "SettingsObserver.h"
-#include "ThemeHelper.h"
#include
#include
-#include
#include
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 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.");
- }
- }
-}
\ No newline at end of file
diff --git a/src/modules/LightSwitch/LightSwitchService/LightSwitchSettings.h b/src/modules/LightSwitch/LightSwitchService/LightSwitchSettings.h
index 25f60cec82..d4029d072d 100644
--- a/src/modules/LightSwitch/LightSwitchService/LightSwitchSettings.h
+++ b/src/modules/LightSwitch/LightSwitchService/LightSwitchSettings.h
@@ -81,7 +81,6 @@ public:
void RemoveObserver(SettingsObserver& observer);
void LoadSettings();
- void ApplyThemeIfNecessary();
HANDLE GetSettingsChangedEvent() const;
diff --git a/src/modules/LightSwitch/LightSwitchService/LightSwitchStateManager.cpp b/src/modules/LightSwitch/LightSwitchService/LightSwitchStateManager.cpp
new file mode 100644
index 0000000000..836d511159
--- /dev/null
+++ b/src/modules/LightSwitch/LightSwitchService/LightSwitchStateManager.cpp
@@ -0,0 +1,243 @@
+#include "pch.h"
+#include "LightSwitchStateManager.h"
+#include
+#include
+#include "ThemeScheduler.h"
+#include
+
+void ApplyTheme(bool shouldBeLight);
+
+// Constructor
+LightSwitchStateManager::LightSwitchStateManager()
+{
+ Logger::info(L"[LightSwitchStateManager] Initialized");
+}
+
+// Called when settings.json changes
+void LightSwitchStateManager::OnSettingsChanged()
+{
+ std::lock_guard 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 lock(_stateMutex);
+ Logger::debug(L"[LightSwitchStateManager] Tick received: {}", currentMinutes);
+ EvaluateAndApplyIfNeeded();
+}
+
+// Called when manual override is triggered
+void LightSwitchStateManager::OnManualOverride()
+{
+ std::lock_guard 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 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 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;
+}
+
+
+
diff --git a/src/modules/LightSwitch/LightSwitchService/LightSwitchStateManager.h b/src/modules/LightSwitch/LightSwitchService/LightSwitchStateManager.h
new file mode 100644
index 0000000000..5c9bcc6e25
--- /dev/null
+++ b/src/modules/LightSwitch/LightSwitchService/LightSwitchStateManager.h
@@ -0,0 +1,47 @@
+#pragma once
+#include "LightSwitchSettings.h"
+#include
+
+// 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);
+};
diff --git a/src/modules/LightSwitch/LightSwitchService/LightSwitchUtils.h b/src/modules/LightSwitch/LightSwitchService/LightSwitchUtils.h
new file mode 100644
index 0000000000..0f4462bb65
--- /dev/null
+++ b/src/modules/LightSwitch/LightSwitchService/LightSwitchUtils.h
@@ -0,0 +1,24 @@
+#pragma once
+#include
+
+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;
+}
diff --git a/src/modules/LightSwitch/Tests/LightSwitch.UITests/TestHelper.cs b/src/modules/LightSwitch/Tests/LightSwitch.UITests/TestHelper.cs
index 7b586301f6..37041b4b2d 100644
--- a/src/modules/LightSwitch/Tests/LightSwitch.UITests/TestHelper.cs
+++ b/src/modules/LightSwitch/Tests/LightSwitch.UITests/TestHelper.cs
@@ -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(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(By.AccessibilityId("Timeline_LightSwitch"), 5000);
Assert.IsNotNull(timeline, "Timeline not found.");
@@ -198,7 +198,7 @@ namespace LightSwitch.UITests
}
///
- /// Perform a update geolocation test operation
+ /// Perform a update manual location test operation
///
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(By.AccessibilityId("SetLocationButton_LightSwitch"), 5000);
Assert.IsNotNull(setLocationButton, "Set location button not found.");
- setLocationButton.Click();
+ setLocationButton.Click(msPostAction: 1000);
- var autoSuggestTextbox = testBase.Session.Find(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(By.AccessibilityId("LatitudeBox_LightSwitch"), 5000);
+ Assert.IsNotNull(latitudeBox, "Latitude text box not found.");
+ latitudeBox.Click();
- var latLong = testBase.Session.Find(By.AccessibilityId("LocationResultText_LightSwitch"), 5000);
- Assert.IsFalse(string.IsNullOrWhiteSpace(latLong.Text));
+ testBase.Session.SendKeys(Key.Up);
+
+ var longitudeBox = testBase.Session.Find(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(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(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(By.AccessibilityId("LocationResultText_LightSwitch"), 5000);
- Assert.IsFalse(string.IsNullOrWhiteSpace(latLong.Text));
+ var syncLocationButton = testBase.Session.Find(By.AccessibilityId("SyncLocationButton_LightSwitch"), 5000);
+ Assert.IsNotNull(syncLocationButton, "Sync location button not found.");
+ syncLocationButton.Click(msPostAction: 8000);
var sunrise = testBase.Session.Find(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();
diff --git a/src/modules/MouseUtils/CursorWrap/dllmain.cpp b/src/modules/MouseUtils/CursorWrap/dllmain.cpp
index ece1948d01..74524ed9f9 100644
--- a/src/modules/MouseUtils/CursorWrap/dllmain.cpp
+++ b/src/modules/MouseUtils/CursorWrap/dllmain.cpp
@@ -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
diff --git a/src/modules/MouseUtils/FindMyMouse/FindMyMouse.vcxproj b/src/modules/MouseUtils/FindMyMouse/FindMyMouse.vcxproj
index 85cdddfa0d..695bbb8b44 100644
--- a/src/modules/MouseUtils/FindMyMouse/FindMyMouse.vcxproj
+++ b/src/modules/MouseUtils/FindMyMouse/FindMyMouse.vcxproj
@@ -64,6 +64,7 @@
true
_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)
true
+ MultiThreadedDebug
stdcpplatest
@@ -81,6 +82,7 @@
true
NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)
true
+ MultiThreaded
stdcpplatest
diff --git a/src/modules/MouseUtils/MouseHighlighter/MouseHighlighter.vcxproj b/src/modules/MouseUtils/MouseHighlighter/MouseHighlighter.vcxproj
index 779fa0f044..31306909e3 100644
--- a/src/modules/MouseUtils/MouseHighlighter/MouseHighlighter.vcxproj
+++ b/src/modules/MouseUtils/MouseHighlighter/MouseHighlighter.vcxproj
@@ -48,6 +48,7 @@
true
_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)
true
+ MultiThreadedDebug
stdcpplatest
@@ -65,6 +66,7 @@
true
NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)
true
+ MultiThreaded
stdcpplatest
diff --git a/src/modules/MouseUtils/MouseJump/MouseJump.vcxproj b/src/modules/MouseUtils/MouseJump/MouseJump.vcxproj
index 5f76d95bc9..08443dffb2 100644
--- a/src/modules/MouseUtils/MouseJump/MouseJump.vcxproj
+++ b/src/modules/MouseUtils/MouseJump/MouseJump.vcxproj
@@ -48,6 +48,7 @@
true
_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)
true
+ MultiThreadedDebug
stdcpplatest
@@ -65,6 +66,7 @@
true
NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)
true
+ MultiThreaded
stdcpplatest
diff --git a/src/modules/MouseUtils/MousePointerCrosshairs/MousePointerCrosshairs.vcxproj b/src/modules/MouseUtils/MousePointerCrosshairs/MousePointerCrosshairs.vcxproj
index f89386be55..bcb0b4891f 100644
--- a/src/modules/MouseUtils/MousePointerCrosshairs/MousePointerCrosshairs.vcxproj
+++ b/src/modules/MouseUtils/MousePointerCrosshairs/MousePointerCrosshairs.vcxproj
@@ -49,6 +49,7 @@
true
_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)
true
+ MultiThreadedDebug
stdcpplatest
@@ -66,6 +67,7 @@
true
NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)
true
+ MultiThreaded
stdcpplatest
diff --git a/src/modules/NewPlus/NewShellExtensionContextMenu.win10/NewPlus.ShellExtension.win10.vcxproj b/src/modules/NewPlus/NewShellExtensionContextMenu.win10/NewPlus.ShellExtension.win10.vcxproj
index 0c8d98bc36..0eaa9de899 100644
--- a/src/modules/NewPlus/NewShellExtensionContextMenu.win10/NewPlus.ShellExtension.win10.vcxproj
+++ b/src/modules/NewPlus/NewShellExtensionContextMenu.win10/NewPlus.ShellExtension.win10.vcxproj
@@ -46,7 +46,7 @@
_DEBUG;NEWPLUSSHELLEXTENSIONWIN10_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)
true
Use
- ..\..\..\common\Telemetry;..\..\;..\..\..\;%(AdditionalIncludeDirectories);..\NewShellExtensionContextMenu
+ ..\..\..\common\Telemetry;..\..\;..\..\..\;%(AdditionalIncludeDirectories);..\NewShellExtensionContextMenu;$(MSBuildThisFileDirectory)
false
stdcpplatest
@@ -67,7 +67,7 @@
NDEBUG;NEWPLUSSHELLEXTENSIONWIN10_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)
true
Use
- ..\..\..\common\Telemetry;..\..\;..\..\..\;%(AdditionalIncludeDirectories);..\NewShellExtensionContextMenu
+ ..\..\..\common\Telemetry;..\..\;..\..\..\;%(AdditionalIncludeDirectories);..\NewShellExtensionContextMenu;$(MSBuildThisFileDirectory)
false
stdcpplatest
diff --git a/src/modules/NewPlus/NewShellExtensionContextMenu/NewShellExtensionContextMenu.vcxproj b/src/modules/NewPlus/NewShellExtensionContextMenu/NewShellExtensionContextMenu.vcxproj
index 4c54003534..e69a495b81 100644
--- a/src/modules/NewPlus/NewShellExtensionContextMenu/NewShellExtensionContextMenu.vcxproj
+++ b/src/modules/NewPlus/NewShellExtensionContextMenu/NewShellExtensionContextMenu.vcxproj
@@ -67,6 +67,8 @@
false
dll.def
runtimeobject.lib;$(CoreLibraryDependencies)
+
+
del $(OutDir)\NewPlusPackage.msix /q
@@ -98,6 +100,8 @@ MakeAppx.exe pack /d . /p $(OutDir)NewPlusPackage.msix /nv
false
dll.def
runtimeobject.lib;$(CoreLibraryDependencies)
+
+
del $(OutDir)\NewPlusPackage.msix /q
diff --git a/src/modules/Workspaces/WorkspacesLauncher/WorkspacesLauncher.vcxproj b/src/modules/Workspaces/WorkspacesLauncher/WorkspacesLauncher.vcxproj
index dc7d7d52d9..c8cdca502e 100644
--- a/src/modules/Workspaces/WorkspacesLauncher/WorkspacesLauncher.vcxproj
+++ b/src/modules/Workspaces/WorkspacesLauncher/WorkspacesLauncher.vcxproj
@@ -29,6 +29,7 @@
_DEBUG;%(PreprocessorDefinitions)
Disabled
true
+ MultiThreadedDebug
true
@@ -39,6 +40,7 @@
NDEBUG;%(PreprocessorDefinitions)
MaxSpeed
false
+ MultiThreaded
true
true
diff --git a/src/modules/Workspaces/WorkspacesSnapshotTool/WorkspacesSnapshotTool.vcxproj b/src/modules/Workspaces/WorkspacesSnapshotTool/WorkspacesSnapshotTool.vcxproj
index 6d0bc6e1f6..e3ce44a368 100644
--- a/src/modules/Workspaces/WorkspacesSnapshotTool/WorkspacesSnapshotTool.vcxproj
+++ b/src/modules/Workspaces/WorkspacesSnapshotTool/WorkspacesSnapshotTool.vcxproj
@@ -29,6 +29,7 @@
_DEBUG;%(PreprocessorDefinitions)
Disabled
true
+ MultiThreadedDebug
true
@@ -39,6 +40,7 @@
NDEBUG;%(PreprocessorDefinitions)
MaxSpeed
false
+ MultiThreaded
true
true
diff --git a/src/modules/Workspaces/WorkspacesWindowArranger/WorkspacesWindowArranger.vcxproj b/src/modules/Workspaces/WorkspacesWindowArranger/WorkspacesWindowArranger.vcxproj
index 4fbc691abd..7681359f21 100644
--- a/src/modules/Workspaces/WorkspacesWindowArranger/WorkspacesWindowArranger.vcxproj
+++ b/src/modules/Workspaces/WorkspacesWindowArranger/WorkspacesWindowArranger.vcxproj
@@ -29,6 +29,7 @@
_DEBUG;%(PreprocessorDefinitions)
Disabled
true
+ MultiThreadedDebug
true
@@ -39,6 +40,7 @@
NDEBUG;%(PreprocessorDefinitions)
MaxSpeed
false
+ MultiThreaded
true
true
diff --git a/src/modules/ZoomIt/ZoomIt/GifRecordingSession.cpp b/src/modules/ZoomIt/ZoomIt/GifRecordingSession.cpp
new file mode 100644
index 0000000000..22e2079f71
--- /dev/null
+++ b/src/modules/ZoomIt/ZoomIt/GifRecordingSession.cpp
@@ -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
+
+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(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(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 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 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(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(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::Create(
+ winrt::IDirect3DDevice const& device,
+ winrt::GraphicsCaptureItem const& item,
+ RECT const& crop,
+ uint32_t frameRate,
+ winrt::Streams::IRandomAccessStream const& stream)
+{
+ return std::shared_ptr(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(m_width) || frameDesc.Height > static_cast(m_height))
+ {
+ float scaleX = static_cast(m_width) / frameDesc.Width;
+ float scaleY = static_cast(m_height) / frameDesc.Height;
+ float scale = min(scaleX, scaleY);
+
+ targetWidth = static_cast(frameDesc.Width * scale);
+ targetHeight = static_cast(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 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 frameEncode;
+ winrt::com_ptr 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 sourceBitmap;
+ winrt::check_hresult(m_wicFactory->CreateBitmapFromMemory(
+ frameDesc.Width,
+ frameDesc.Height,
+ GUID_WICPixelFormat32bppBGRA,
+ mappedResource.RowPitch,
+ frameDesc.Height * mappedResource.RowPitch,
+ static_cast(mappedResource.pData),
+ sourceBitmap.put()));
+
+ // If we need downsampling, use WIC scaler
+ winrt::com_ptr finalSource = sourceBitmap;
+ if (targetWidth != frameDesc.Width || targetHeight != frameDesc.Height)
+ {
+ winrt::com_ptr 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 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(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 lastCroppedTexture;
+
+ while (m_isRecording && !m_closed)
+ {
+ captureAttempts++;
+ auto frame = m_frameWait->TryGetNextFrame();
+
+ winrt::com_ptr croppedTexture;
+
+ if (frame)
+ {
+ successfulCaptures++;
+ auto contentSize = frame->ContentSize;
+ auto frameTexture = GetDXGIInterfaceFromObject(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(0), static_cast(desc.Width));
+ region.right = std::clamp(m_rcCrop.left + width, static_cast(0), static_cast(desc.Width));
+ region.top = std::clamp(m_rcCrop.top, static_cast(0), static_cast(desc.Height));
+ region.bottom = std::clamp(m_rcCrop.top + height, static_cast(0), static_cast(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(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();
+}
diff --git a/src/modules/ZoomIt/ZoomIt/GifRecordingSession.h b/src/modules/ZoomIt/ZoomIt/GifRecordingSession.h
new file mode 100644
index 0000000000..90732f60f3
--- /dev/null
+++ b/src/modules/ZoomIt/ZoomIt/GifRecordingSession.h
@@ -0,0 +1,69 @@
+//==============================================================================
+//
+// Zoomit
+// Sysinternals - www.sysinternals.com
+//
+// GIF recording support using Windows Imaging Component (WIC)
+//
+//==============================================================================
+#pragma once
+
+#include "CaptureFrameWait.h"
+#include
+#include
+
+class GifRecordingSession : public std::enable_shared_from_this
+{
+public:
+ [[nodiscard]] static std::shared_ptr 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 m_d3dDevice;
+ winrt::com_ptr m_d3dContext;
+ RECT m_rcCrop;
+ uint32_t m_frameRate;
+
+ winrt::GraphicsCaptureItem m_item{ nullptr };
+ winrt::GraphicsCaptureItem::Closed_revoker m_itemClosed;
+ std::shared_ptr m_frameWait;
+
+ winrt::Streams::IRandomAccessStream m_stream{ nullptr };
+
+ // WIC components for GIF encoding
+ winrt::com_ptr m_wicFactory;
+ winrt::com_ptr m_wicStream;
+ winrt::com_ptr m_gifEncoder;
+ winrt::com_ptr m_encoderMetadataWriter;
+
+ std::atomic m_isRecording = false;
+ std::atomic 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;
+};
diff --git a/src/modules/ZoomIt/ZoomIt/ZoomIt.rc b/src/modules/ZoomIt/ZoomIt/ZoomIt.rc
index 382c93208e..99bdb66b58 100644
--- a/src/modules/ZoomIt/ZoomIt/ZoomIt.rc
+++ b/src/modules/ZoomIt/ZoomIt/ZoomIt.rc
@@ -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 "Sysinternals - www.sysinternals.com",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
diff --git a/src/modules/ZoomIt/ZoomIt/ZoomIt.vcxproj b/src/modules/ZoomIt/ZoomIt/ZoomIt.vcxproj
index dc5acb5fce..c9100f342e 100644
--- a/src/modules/ZoomIt/ZoomIt/ZoomIt.vcxproj
+++ b/src/modules/ZoomIt/ZoomIt/ZoomIt.vcxproj
@@ -81,6 +81,7 @@
MaxSpeed
__ZOOMIT_POWERTOYS__;_UNICODE;UNICODE;WINVER=0x602;NDEBUG;_WIN32_WINNT=0x602;_WIN32_WINDOWS=0x501;WIN32;_WINDOWS;MSVC6=1;_CRT_SECURE_NO_DEPRECATE;%(PreprocessorDefinitions)
true
+ MultiThreaded
true
@@ -102,6 +103,7 @@
MaxSpeed
__ZOOMIT_POWERTOYS__;_UNICODE;UNICODE;WINVER=0x602;NDEBUG;_WIN32_WINNT=0x602;_WIN32_WINDOWS=0x501;WIN32;_WINDOWS;MSVC6=1;_CRT_SECURE_NO_DEPRECATE;%(PreprocessorDefinitions)
true
+ MultiThreaded
true
@@ -124,6 +126,7 @@
MaxSpeed
__ZOOMIT_POWERTOYS__;_UNICODE;UNICODE;WINVER=0x602;NDEBUG;_WIN32_WINNT=0x602;_WIN32_WINDOWS=0x501;WIN32;_WINDOWS;MSVC6=1;_CRT_SECURE_NO_DEPRECATE;%(PreprocessorDefinitions)
true
+ MultiThreaded
true
@@ -145,6 +148,7 @@
Disabled
__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)
EnableFastChecks
+ MultiThreadedDebug
_DEBUG;_M_IX86;%(PreprocessorDefinitions)
@@ -165,6 +169,7 @@
Disabled
__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)
EnableFastChecks
+ MultiThreadedDebug
_DEBUG;_M_X64;%(PreprocessorDefinitions)
@@ -186,6 +191,7 @@
Disabled
__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)
EnableFastChecks
+ MultiThreadedDebug
_DEBUG;_M_ARM64;%(PreprocessorDefinitions)
@@ -234,6 +240,7 @@
NotUsing
NotUsing
+
Use
@@ -288,6 +295,7 @@
+
diff --git a/src/modules/ZoomIt/ZoomIt/ZoomIt.vcxproj.filters b/src/modules/ZoomIt/ZoomIt/ZoomIt.vcxproj.filters
index 1754412d2d..e0416fe585 100644
--- a/src/modules/ZoomIt/ZoomIt/ZoomIt.vcxproj.filters
+++ b/src/modules/ZoomIt/ZoomIt/ZoomIt.vcxproj.filters
@@ -54,6 +54,9 @@
Source Files
+
+ Source Files
+
@@ -95,6 +98,9 @@
Header Files
+
+ Header Files
+
diff --git a/src/modules/ZoomIt/ZoomIt/ZoomItSettings.h b/src/modules/ZoomIt/ZoomIt/ZoomItSettings.h
index 96e2f19e5b..486d5a61e7 100644
--- a/src/modules/ZoomIt/ZoomIt/ZoomItSettings.h
+++ b/src/modules/ZoomIt/ZoomIt/ZoomItSettings.h
@@ -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(((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(g_SliderZoomLevel) },
{ L"Font", SETTING_TYPE_BINARY, sizeof g_LogFont, &g_LogFont, static_cast(0) },
{ L"RecordFrameRate", SETTING_TYPE_DWORD, 0, &g_RecordFrameRate, static_cast(g_RecordFrameRate) },
- { L"RecordScaling", SETTING_TYPE_DWORD, 0, &g_RecordScaling, static_cast(g_RecordScaling) },
+ { L"RecordingFormat", SETTING_TYPE_DWORD, 0, &g_RecordingFormat, static_cast(0) },
+ { L"RecordScalingGIF", SETTING_TYPE_DWORD, 0, &g_RecordScalingGIF, static_cast(g_RecordScalingGIF) },
+ { L"RecordScalingMP4", SETTING_TYPE_DWORD, 0, &g_RecordScalingMP4, static_cast(g_RecordScalingMP4) },
{ L"CaptureAudio", SETTING_TYPE_BOOLEAN, 0, &g_CaptureAudio, static_cast(g_CaptureAudio) },
{ L"MicrophoneDeviceId", SETTING_TYPE_STRING, sizeof(g_MicrophoneDeviceId), g_MicrophoneDeviceId, static_cast(0) },
{ NULL, SETTING_TYPE_DWORD, 0, NULL, static_cast(0) }
diff --git a/src/modules/ZoomIt/ZoomIt/Zoomit.cpp b/src/modules/ZoomIt/ZoomIt/Zoomit.cpp
index dcb424accf..440ceeed0d 100644
--- a/src/modules/ZoomIt/ZoomIt/Zoomit.cpp
+++ b/src/modules/ZoomIt/ZoomIt/Zoomit.cpp
@@ -15,6 +15,7 @@
#include "Utility.h"
#include "WindowsVersions.h"
#include "ZoomItSettings.h"
+#include "GifRecordingSession.h"
#ifdef __ZOOMIT_POWERTOYS__
#include
@@ -68,6 +69,8 @@ COLORREF g_CustomColors[16];
#define SNIP_SAVE_HOTKEY 9
#define DEMOTYPE_HOTKEY 10
#define DEMOTYPE_RESET_HOTKEY 11
+#define RECORD_GIF_HOTKEY 12
+#define RECORD_GIF_WINDOW_HOTKEY 13
#define ZOOM_PAGE 0
#define LIVE_PAGE 1
@@ -89,6 +92,11 @@ OPTION_TABS g_OptionsTabs[] = {
{ _T("Snip"), NULL }
};
+static const TCHAR* g_RecordingFormats[] = {
+ _T("GIF"),
+ _T("MP4")
+};
+
float g_ZoomLevels[] = {
1.25,
1.50,
@@ -99,6 +107,8 @@ float g_ZoomLevels[] = {
};
DWORD g_FramerateOptions[] = {
+ 15,
+ 24,
30,
60
};
@@ -120,7 +130,7 @@ const float STRONG_BLUR_RADIUS = 40;
DWORD g_ToggleMod;
DWORD g_LiveZoomToggleMod;
DWORD g_DrawToggleMod;
-DWORD g_BreakToggleMod;
+DWORD g_BreakToggleMod;
DWORD g_DemoTypeToggleMod;
DWORD g_RecordToggleMod;
DWORD g_SnipToggleMod;
@@ -152,12 +162,15 @@ BOOLEAN g_running = TRUE;
// Screen recording globals
#define DEFAULT_RECORDING_FILE L"Recording.mp4"
+#define DEFAULT_GIF_RECORDING_FILE L"Recording.gif"
+
BOOL g_RecordToggle = FALSE;
BOOL g_RecordCropping = FALSE;
SelectRectangle g_SelectRectangle;
std::wstring g_RecordingSaveLocation;
winrt::IDirect3DDevice g_RecordDevice{ nullptr };
std::shared_ptr g_RecordingSession = nullptr;
+std::shared_ptr g_GifRecordingSession = nullptr;
type_pGetMonitorInfo pGetMonitorInfo;
type_MonitorFromPoint pMonitorFromPoint;
@@ -192,7 +205,7 @@ ComputerGraphicsInit g_GraphicsInit;
//----------------------------------------------------------------------------
//
-// Saves specified filePath to clipboard.
+// Saves specified filePath to clipboard.
//
//----------------------------------------------------------------------------
bool SaveToClipboard( const WCHAR* filePath, HWND hwnd )
@@ -207,18 +220,18 @@ bool SaveToClipboard( const WCHAR* filePath, HWND hwnd )
HDROP hDrop = static_cast(GlobalAlloc( GHND, size ));
if (hDrop == NULL)
{
- return false;
+ return false;
}
DROPFILES* dFiles = static_cast(GlobalLock( hDrop ));
if (dFiles == NULL)
{
GlobalFree( hDrop );
- return false;
+ return false;
}
dFiles->pFiles = sizeof(DROPFILES);
- dFiles->fWide = TRUE;
+ dFiles->fWide = TRUE;
wcscpy( reinterpret_cast(& dFiles[1]), filePath);
GlobalUnlock( hDrop );
@@ -345,7 +358,7 @@ VOID ErrorDialog( HWND hParent, PCTSTR message, DWORD _Error )
TCHAR errmsg[1024];
FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
- NULL, _Error,
+ NULL, _Error,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
reinterpret_cast(&lpMsgBuf), 0, NULL );
_stprintf( errmsg, L"%s: %s", message, lpMsgBuf );
@@ -391,7 +404,7 @@ VOID ErrorDialogString( HWND hParent, PCTSTR Message, const wchar_t *_Error )
// SetAutostartFilePath
//
// Sets the file path for later autostart config.
-//
+//
//--------------------------------------------------------------------
void SetAutostartFilePath()
{
@@ -417,32 +430,32 @@ void SetAutostartFilePath()
// ConfigureAutostart
//
// Enables or disables Zoomit autostart for the current image file.
-//
+//
//--------------------------------------------------------------------
-bool ConfigureAutostart( HWND hParent, bool Enable )
+bool ConfigureAutostart( HWND hParent, bool Enable )
{
HKEY hRunKey, hZoomit;
DWORD error, length, type;
TCHAR imageFile[MAX_PATH];
- error = RegOpenKeyEx( HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\Run",
+ error = RegOpenKeyEx( HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\Run",
0, KEY_SET_VALUE, &hRunKey );
if( error == ERROR_SUCCESS ) {
if( Enable ) {
-
- error = RegOpenKeyEx( HKEY_CURRENT_USER, _T("Software\\Sysinternals\\Zoomit"), 0,
+
+ error = RegOpenKeyEx( HKEY_CURRENT_USER, _T("Software\\Sysinternals\\Zoomit"), 0,
KEY_QUERY_VALUE, &hZoomit );
if( error == ERROR_SUCCESS ) {
length = sizeof(imageFile);
#ifdef _WIN64
// Unconditionally reset filepath in case this was already set by 32 bit version
- SetAutostartFilePath();
+ SetAutostartFilePath();
#endif
error = RegQueryValueEx( hZoomit, _T( "Filepath" ), 0, &type, (BYTE *) imageFile, &length );
RegCloseKey( hZoomit );
- if( error == ERROR_SUCCESS ) {
+ if( error == ERROR_SUCCESS ) {
error = RegSetValueEx( hRunKey, APPNAME, 0, REG_SZ, (BYTE *) imageFile,
static_cast(_tcslen(imageFile)+1) * sizeof(TCHAR));
@@ -454,7 +467,7 @@ bool ConfigureAutostart( HWND hParent, bool Enable )
if( error == ERROR_FILE_NOT_FOUND ) error = ERROR_SUCCESS;
}
RegCloseKey( hRunKey );
- }
+ }
if( error != ERROR_SUCCESS ) {
ErrorDialog( hParent, L"Error configuring auto start", error );
@@ -468,15 +481,15 @@ bool ConfigureAutostart( HWND hParent, bool Enable )
// IsAutostartConfigured
//
// Is this version of zoomit configured to autostart.
-//
+//
//--------------------------------------------------------------------
bool IsAutostartConfigured()
{
HKEY hRunKey;
- TCHAR imageFile[MAX_PATH];
+ TCHAR imageFile[MAX_PATH];
DWORD error, imageFileLength, type;
- error = RegOpenKeyEx( HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\Run",
+ error = RegOpenKeyEx( HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\Run",
0, KEY_QUERY_VALUE, &hRunKey );
if( error == ERROR_SUCCESS ) {
@@ -496,15 +509,15 @@ bool IsAutostartConfigured()
//
// Returns true if this is the 32-bit version of the executable
// and we're on 64-bit Windows.
-//
+//
//--------------------------------------------------------------------
typedef BOOL (__stdcall *P_IS_WOW64PROCESS)(
HANDLE hProcess,
PBOOL Wow64Process
);
-BOOL
+BOOL
RunningOnWin64(
- VOID
+ VOID
)
{
P_IS_WOW64PROCESS pIsWow64Process;
@@ -513,9 +526,9 @@ RunningOnWin64(
pIsWow64Process = (P_IS_WOW64PROCESS) GetProcAddress(GetModuleHandle(_T("kernel32.dll")),
"IsWow64Process");
if( pIsWow64Process ) {
-
+
pIsWow64Process( GetCurrentProcess(), &isWow64 );
- }
+ }
return isWow64;
}
@@ -524,7 +537,7 @@ RunningOnWin64(
//
// ExtractImageResource
//
-// Extracts the specified file that is located in a resource for
+// Extracts the specified file that is located in a resource for
// this executable.
//
//--------------------------------------------------------------------
@@ -532,15 +545,15 @@ BOOLEAN ExtractImageResource( PTCHAR ResourceName, PTCHAR TargetFile )
{
HRSRC hResource;
HGLOBAL hImageResource;
- DWORD dwImageSize;
+ DWORD dwImageSize;
LPVOID lpvImage;
FILE *hFile;
// Locate the resource
- hResource = FindResource( NULL, ResourceName, _T("BINRES") );
- if( !hResource )
+ hResource = FindResource( NULL, ResourceName, _T("BINRES") );
+ if( !hResource )
return FALSE;
-
+
hImageResource = LoadResource( NULL, hResource );
dwImageSize = SizeofResource( NULL, hResource );
lpvImage = LockResource( hImageResource );
@@ -562,10 +575,10 @@ BOOLEAN ExtractImageResource( PTCHAR ResourceName, PTCHAR TargetFile )
//
// Returns true if this is the 32-bit version of the executable
// and we're on 64-bit Windows.
-//
+//
//--------------------------------------------------------------------
-DWORD
-Run64bitVersion(
+DWORD
+Run64bitVersion(
void
)
{
@@ -636,13 +649,13 @@ BOOLEAN IsPresentationMode()
//----------------------------------------------------------------------------
//
// EnableDisableSecondaryDisplay
-//
+//
// Creates a second display on the secondary monitor for displaying the
-// break timer.
+// break timer.
//
//----------------------------------------------------------------------------
-LONG EnableDisableSecondaryDisplay( HWND hWnd, BOOLEAN Enable,
- PDEVMODE OriginalDevMode )
+LONG EnableDisableSecondaryDisplay( HWND hWnd, BOOLEAN Enable,
+ PDEVMODE OriginalDevMode )
{
LONG result;
DEVMODE devMode{};
@@ -654,7 +667,7 @@ LONG EnableDisableSecondaryDisplay( HWND hWnd, BOOLEAN Enable,
//
devMode.dmSize = sizeof(devMode);
devMode.dmDriverExtra = 0;
- EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &devMode);
+ EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &devMode);
*OriginalDevMode = devMode;
//
@@ -667,7 +680,7 @@ LONG EnableDisableSecondaryDisplay( HWND hWnd, BOOLEAN Enable,
DM_PELSWIDTH |
DM_PELSHEIGHT |
DM_DISPLAYFLAGS |
- DM_DISPLAYFREQUENCY;
+ DM_DISPLAYFREQUENCY;
result = ChangeDisplaySettingsEx( L"\\\\.\\DISPLAY2",
&devMode,
NULL,
@@ -723,11 +736,11 @@ LONG EnableDisableSecondaryDisplay( HWND hWnd, BOOLEAN Enable,
// GetLineBounds
//
// Gets the rectangle bounding a line, taking into account pen width
-//
+//
//----------------------------------------------------------------------------
Gdiplus::Rect GetLineBounds( POINT p1, POINT p2, int penWidth )
{
- Gdiplus::Rect rect( min(p1.x, p2.x), min(p1.y, p2.y),
+ Gdiplus::Rect rect( min(p1.x, p2.x), min(p1.y, p2.y),
abs(p1.x - p2.x), abs( p1.y - p2.y));
rect.Inflate( penWidth, penWidth );
return rect;
@@ -738,7 +751,7 @@ Gdiplus::Rect GetLineBounds( POINT p1, POINT p2, int penWidth )
// InvalidateGdiplusRect
//
// Invalidate portion of window specified by Gdiplus::Rect
-//
+//
//----------------------------------------------------------------------------
void InvalidateGdiplusRect(HWND hWnd, Gdiplus::Rect BoundsRect)
{
@@ -756,8 +769,8 @@ void InvalidateGdiplusRect(HWND hWnd, Gdiplus::Rect BoundsRect)
//
// CreateGdiplusBitmap
//
-// Creates a gdiplus bitmap of the specified region of the HDC.
-//
+// Creates a gdiplus bitmap of the specified region of the HDC.
+//
//----------------------------------------------------------------------------
Gdiplus::Bitmap *CreateGdiplusBitmap( HDC hDc, int x, int y, int Width, int Height )
{
@@ -772,7 +785,7 @@ Gdiplus::Bitmap *CreateGdiplusBitmap( HDC hDc, int x, int y, int Width, int Heig
Gdiplus::Bitmap *blurBitmap = new Gdiplus::Bitmap(hBitmap, NULL);
DeleteDC(hdcNewBitmap);
DeleteObject(hBitmap);
- return blurBitmap;
+ return blurBitmap;
}
@@ -781,7 +794,7 @@ Gdiplus::Bitmap *CreateGdiplusBitmap( HDC hDc, int x, int y, int Width, int Heig
// CreateBitmapMemoryDIB
//
// Creates a memory DC and DIB for the specified region of the screen.
-//
+//
//----------------------------------------------------------------------------
BYTE* CreateBitmapMemoryDIB(HDC hdcScreenCompat, HDC hBitmapDc, Gdiplus::Rect* lineBounds,
HDC* hdcMem, HBITMAP* hDIBOrig, HBITMAP* hPreviousBitmap)
@@ -819,8 +832,8 @@ BYTE* CreateBitmapMemoryDIB(HDC hdcScreenCompat, HDC hBitmapDc, Gdiplus::Rect* l
//
// LockGdiPlusBitmap
//
-// Locks the Gdi+ bitmap so that we can access its pixels in memory.
-//
+// Locks the Gdi+ bitmap so that we can access its pixels in memory.
+//
//----------------------------------------------------------------------------
#ifdef _MSC_VER
// Analyzers want us to use a scoped object instead of new. But given all the operations done in Bitmaps it seems better to leave it as a heap object.
@@ -835,7 +848,7 @@ Gdiplus::BitmapData* LockGdiPlusBitmap(Gdiplus::Bitmap* Bitmap)
Gdiplus::Rect lineBitmapBounds(0, 0, Bitmap->GetWidth(), Bitmap->GetHeight());
Bitmap->LockBits(&lineBitmapBounds, Gdiplus::ImageLockModeRead,
Bitmap->GetPixelFormat(), lineData);
- return lineData;
+ return lineData;
}
#ifdef _MSC_VER
#pragma warning(pop)
@@ -846,11 +859,11 @@ Gdiplus::BitmapData* LockGdiPlusBitmap(Gdiplus::Bitmap* Bitmap)
//
// BlurScreen
//
-// Blur the portion of the screen by copying a blurred bitmap with the
-// specified shape.
-//
+// Blur the portion of the screen by copying a blurred bitmap with the
+// specified shape.
+//
//----------------------------------------------------------------------------
-void BlurScreen(HDC hdcScreenCompat, Gdiplus::Rect* lineBounds,
+void BlurScreen(HDC hdcScreenCompat, Gdiplus::Rect* lineBounds,
Gdiplus::Bitmap *BlurBitmap, BYTE* pPixels)
{
HDC hdcDIB;
@@ -896,8 +909,8 @@ void BlurScreen(HDC hdcScreenCompat, Gdiplus::Rect* lineBounds,
//
// BitmapBlur
//
-// Blurs the bitmap.
-//
+// Blurs the bitmap.
+//
//----------------------------------------------------------------------------
void BitmapBlur(Gdiplus::Bitmap* hBitmap)
{
@@ -928,7 +941,7 @@ void BitmapBlur(Gdiplus::Bitmap* hBitmap)
// DrawBlurredShape
//
// Blur a shaped region of the screen.
-//
+//
//----------------------------------------------------------------------------
void DrawBlurredShape( DWORD Shape, Gdiplus::Pen *pen, HDC hdcScreenCompat, Gdiplus::Graphics *dstGraphics,
int x1, int y1, int x2, int y2)
@@ -937,7 +950,7 @@ void DrawBlurredShape( DWORD Shape, Gdiplus::Pen *pen, HDC hdcScreenCompat, Gdip
Gdiplus::Rect lineBounds( min( x1, x2 ), min( y1, y2 ), abs( x2 - x1 ), abs( y2 - y1 ) );
// Expand for line drawing
- if (Shape == DRAW_LINE)
+ if (Shape == DRAW_LINE)
lineBounds.Inflate( static_cast(g_PenWidth / 2), static_cast(g_PenWidth / 2) );
Gdiplus::Bitmap* lineBitmap = new Gdiplus::Bitmap(lineBounds.Width, lineBounds.Height, PixelFormat32bppARGB);
@@ -978,7 +991,7 @@ void DrawBlurredShape( DWORD Shape, Gdiplus::Pen *pen, HDC hdcScreenCompat, Gdip
// CreateDrawingBitmap
//
// Create a bitmap to draw on.
-//
+//
//----------------------------------------------------------------------------
Gdiplus::Bitmap* CreateDrawingBitmap(Gdiplus::Rect lineBounds )
{
@@ -992,8 +1005,8 @@ Gdiplus::Bitmap* CreateDrawingBitmap(Gdiplus::Rect lineBounds )
//
// DrawBitmapLine
//
-// Creates a bitmap and draws a line on it.
-//
+// Creates a bitmap and draws a line on it.
+//
//----------------------------------------------------------------------------
Gdiplus::Bitmap* DrawBitmapLine(Gdiplus::Rect lineBounds, POINT p1, POINT p2, Gdiplus::Pen *pen)
{
@@ -1013,7 +1026,7 @@ Gdiplus::Bitmap* DrawBitmapLine(Gdiplus::Rect lineBounds, POINT p1, POINT p2, Gd
// ColorFromColorRef
//
// Returns a color object from the colorRef that includes the alpha channel
-//
+//
//----------------------------------------------------------------------------
Gdiplus::Color ColorFromColorRef(DWORD colorRef) {
BYTE a = (colorRef >> 24) & 0xFF; // Extract the alpha channel value
@@ -1028,8 +1041,8 @@ Gdiplus::Color ColorFromColorRef(DWORD colorRef) {
//
// AdjustHighlighterColor
//
-// Lighten the color.
-//
+// Lighten the color.
+//
//----------------------------------------------------------------------------
void AdjustHighlighterColor(BYTE* red, BYTE* green, BYTE* blue) {
@@ -1044,8 +1057,8 @@ void AdjustHighlighterColor(BYTE* red, BYTE* green, BYTE* blue) {
// BlendColors
//
// Blends two colors together using the alpha channel of the second color.
-// The highlighter is the second color.
-//
+// The highlighter is the second color.
+//
//----------------------------------------------------------------------------
COLORREF BlendColors(COLORREF color1, const Gdiplus::Color& color2) {
@@ -1068,7 +1081,7 @@ COLORREF BlendColors(COLORREF color1, const Gdiplus::Color& color2) {
// int maxValue = max(red1, max(green1, blue1));
if(TRUE) { // red1 > 0x10 && red1 < 0xC0 && (maxValue - minValue < 0x40)) {
- // This does a standard bright highlight
+ // This does a standard bright highlight
alpha2 = 0;
AdjustHighlighterColor( &red2, &green2, &blue2 );
redResult = red2 & red1;
@@ -1095,7 +1108,7 @@ COLORREF BlendColors(COLORREF color1, const Gdiplus::Color& color2) {
// Draws the shape with the highlighter color.
//
//----------------------------------------------------------------------------
-void DrawHighlightedShape( DWORD Shape, HDC hdcScreenCompat, Gdiplus::Brush *pBrush,
+void DrawHighlightedShape( DWORD Shape, HDC hdcScreenCompat, Gdiplus::Brush *pBrush,
Gdiplus::Pen *pPen, int x1, int y1, int x2, int y2)
{
// Create a new bitmap that's the size of the area covered by the line + 2 * g_PenWidth
@@ -1115,7 +1128,7 @@ void DrawHighlightedShape( DWORD Shape, HDC hdcScreenCompat, Gdiplus::Brush *pBr
break;
case DRAW_ELLIPSE:
lineGraphics.FillEllipse( pBrush, 0, 0, lineBounds.Width, lineBounds.Height);
- break;
+ break;
case DRAW_LINE:
lineGraphics.DrawLine(pPen, x1 - lineBounds.X, y1 - lineBounds.Y, x2 - lineBounds.X, y2 - lineBounds.Y);
break;
@@ -1197,7 +1210,7 @@ void DrawHighlightedShape( DWORD Shape, HDC hdcScreenCompat, Gdiplus::Brush *pBr
//
// CreateFadedDesktopBackground
//
-// Creates a snapshot of the desktop that's faded and alpha blended with
+// Creates a snapshot of the desktop that's faded and alpha blended with
// black.
//
//----------------------------------------------------------------------------
@@ -1211,7 +1224,7 @@ HBITMAP CreateFadedDesktopBackground( HDC hdc, LPRECT rcScreen, LPRECT rcCrop )
HBITMAP hBitmap = CreateCompatibleBitmap( hdcScreen, width, height );
HBITMAP hOld = static_cast(SelectObject( hdcMem, hBitmap ));
HBRUSH hBrush = CreateSolidBrush(RGB(0, 0, 0));
-
+
// start with black background
FillRect( hdcMem, rcScreen, hBrush );
if(rcCrop != NULL && rcCrop->left != -1 ) {
@@ -1227,8 +1240,8 @@ HBITMAP CreateFadedDesktopBackground( HDC hdc, LPRECT rcScreen, LPRECT rcCrop )
blend.BlendFlags = 0;
blend.SourceConstantAlpha = 0x4F;
blend.AlphaFormat = 0;
- AlphaBlend( hdcMem,0, 0, width, height,
- hdcScreen, rcScreen->left, rcScreen->top,
+ AlphaBlend( hdcMem,0, 0, width, height,
+ hdcScreen, rcScreen->left, rcScreen->top,
width, height, blend );
SelectObject( hdcMem, hOld );
@@ -1249,9 +1262,9 @@ HBITMAP CreateFadedDesktopBackground( HDC hdc, LPRECT rcScreen, LPRECT rcCrop )
void AdjustToMoveBoundary( float zoomLevel, int *coordinate, int cursor, int size, int max )
{
int diff = static_cast (static_cast(size)/ static_cast(LIVEZOOM_MOVE_REGIONS));
- if( cursor - *coordinate < diff )
- *coordinate = max( 0, cursor - diff );
- else if( (*coordinate + size) - cursor < diff )
+ if( cursor - *coordinate < diff )
+ *coordinate = max( 0, cursor - diff );
+ else if( (*coordinate + size) - cursor < diff )
*coordinate = min( cursor + diff - size, max - size );
}
@@ -1278,10 +1291,10 @@ void GetZoomedTopLeftCoordinates( float zoomLevel, POINT *cursorPos, int *x, int
//
// ScaleImage
//
-// Use gdi+ for anti-aliased bitmap stretching.
+// Use gdi+ for anti-aliased bitmap stretching.
//
//----------------------------------------------------------------------------
-void ScaleImage( HDC hdcDst, float xDst, float yDst, float wDst, float hDst,
+void ScaleImage( HDC hdcDst, float xDst, float yDst, float wDst, float hDst,
HBITMAP bmSrc, float xSrc, float ySrc, float wSrc, float hSrc )
{
Gdiplus::Graphics dstGraphics( hdcDst );
@@ -1331,7 +1344,7 @@ using namespace Gdiplus;
*pClsid = pImageCodecInfo[j].Clsid;
free(pImageCodecInfo);
return j; // Success
- }
+ }
}
free(pImageCodecInfo);
@@ -1339,20 +1352,20 @@ using namespace Gdiplus;
}
//----------------------------------------------------------------------
-//
+//
// ConvertToUnicode
//
//----------------------------------------------------------------------
-void
-ConvertToUnicode(
- PCHAR aString,
- PWCHAR wString,
- DWORD wStringLength
+void
+ConvertToUnicode(
+ PCHAR aString,
+ PWCHAR wString,
+ DWORD wStringLength
)
{
size_t len;
- len = MultiByteToWideChar( CP_ACP, 0, aString, static_cast(strlen(aString)),
+ len = MultiByteToWideChar( CP_ACP, 0, aString, static_cast(strlen(aString)),
wString, wStringLength );
wString[len] = 0;
}
@@ -1411,14 +1424,14 @@ void EnableDisableTrayIcon( HWND hWnd, BOOLEAN Enable )
NOTIFYICONDATA tNotifyIconData;
memset( &tNotifyIconData, 0, sizeof(tNotifyIconData));
- tNotifyIconData.cbSize = sizeof(NOTIFYICONDATA);
- tNotifyIconData.hWnd = hWnd;
- tNotifyIconData.uID = 1;
- tNotifyIconData.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP;
- tNotifyIconData.uCallbackMessage = WM_USER_TRAY_ACTIVATE;
- tNotifyIconData.hIcon = LoadIcon( g_hInstance, L"APPICON" );
+ tNotifyIconData.cbSize = sizeof(NOTIFYICONDATA);
+ tNotifyIconData.hWnd = hWnd;
+ tNotifyIconData.uID = 1;
+ tNotifyIconData.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP;
+ tNotifyIconData.uCallbackMessage = WM_USER_TRAY_ACTIVATE;
+ tNotifyIconData.hIcon = LoadIcon( g_hInstance, L"APPICON" );
lstrcpyn(tNotifyIconData.szTip, APPNAME, sizeof(APPNAME));
- Shell_NotifyIcon(Enable ? NIM_ADD : NIM_DELETE, &tNotifyIconData);
+ Shell_NotifyIcon(Enable ? NIM_ADD : NIM_DELETE, &tNotifyIconData);
}
//----------------------------------------------------------------------------
@@ -1426,7 +1439,7 @@ void EnableDisableTrayIcon( HWND hWnd, BOOLEAN Enable )
// EnableDisableOpacity
//
//----------------------------------------------------------------------------
-void EnableDisableOpacity( HWND hWnd, BOOLEAN Enable )
+void EnableDisableOpacity( HWND hWnd, BOOLEAN Enable )
{
DWORD exStyle;
@@ -1453,11 +1466,11 @@ void EnableDisableOpacity( HWND hWnd, BOOLEAN Enable )
// EnableDisableScreenSaver
//
//----------------------------------------------------------------------------
-void EnableDisableScreenSaver( BOOLEAN Enable )
+void EnableDisableScreenSaver( BOOLEAN Enable )
{
- SystemParametersInfo(SPI_SETSCREENSAVEACTIVE,Enable,0,0);
- SystemParametersInfo(SPI_SETPOWEROFFACTIVE,Enable,0,0);
- SystemParametersInfo(SPI_SETLOWPOWERACTIVE,Enable,0,0);
+ SystemParametersInfo(SPI_SETSCREENSAVEACTIVE,Enable,0,0);
+ SystemParametersInfo(SPI_SETPOWEROFFACTIVE,Enable,0,0);
+ SystemParametersInfo(SPI_SETLOWPOWERACTIVE,Enable,0,0);
}
//----------------------------------------------------------------------------
@@ -1470,25 +1483,25 @@ void EnableDisableStickyKeys( BOOLEAN Enable )
static STICKYKEYS prevStickyKeyValue = {0};
STICKYKEYS newStickyKeyValue = {0};
- // Need to do this on Vista tablet to stop sticky key popup when you
+ // Need to do this on Vista tablet to stop sticky key popup when you
// hold down the shift key and draw with the pen.
if( Enable ) {
if( prevStickyKeyValue.cbSize == sizeof(STICKYKEYS)) {
- SystemParametersInfo(SPI_SETSTICKYKEYS,
+ SystemParametersInfo(SPI_SETSTICKYKEYS,
sizeof(STICKYKEYS), &prevStickyKeyValue, SPIF_SENDCHANGE);
}
} else {
prevStickyKeyValue.cbSize = sizeof(STICKYKEYS);
- if (SystemParametersInfo(SPI_GETSTICKYKEYS, sizeof(STICKYKEYS),
+ if (SystemParametersInfo(SPI_GETSTICKYKEYS, sizeof(STICKYKEYS),
&prevStickyKeyValue, 0)) {
newStickyKeyValue.cbSize = sizeof(STICKYKEYS);
newStickyKeyValue.dwFlags = 0;
- if( !SystemParametersInfo(SPI_SETSTICKYKEYS,
+ if( !SystemParametersInfo(SPI_SETSTICKYKEYS,
sizeof(STICKYKEYS), &newStickyKeyValue, SPIF_SENDCHANGE)) {
// DWORD error = GetLastError();
@@ -1520,7 +1533,7 @@ constexpr DWORD GetKeyMod( DWORD Key )
// AdvancedBreakProc
//
//----------------------------------------------------------------------------
-INT_PTR CALLBACK AdvancedBreakProc( HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam )
+INT_PTR CALLBACK AdvancedBreakProc( HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam )
{
TCHAR opacity[10];
static TCHAR newSoundFile[MAX_PATH];
@@ -1535,9 +1548,9 @@ INT_PTR CALLBACK AdvancedBreakProc( HWND hDlg, UINT message, WPARAM wParam, LPAR
pSHAutoComplete( GetDlgItem( hDlg, IDC_SOUND_FILE), SHACF_FILESYSTEM );
pSHAutoComplete( GetDlgItem( hDlg, IDC_BACKGROUND_FILE), SHACF_FILESYSTEM );
}
- CheckDlgButton( hDlg, IDC_CHECK_BACKGROUND_FILE,
+ CheckDlgButton( hDlg, IDC_CHECK_BACKGROUND_FILE,
g_BreakShowBackgroundFile ? BST_CHECKED: BST_UNCHECKED );
- CheckDlgButton( hDlg, IDC_CHECK_SOUND_FILE,
+ CheckDlgButton( hDlg, IDC_CHECK_SOUND_FILE,
g_BreakPlaySoundFile ? BST_CHECKED: BST_UNCHECKED );
CheckDlgButton( hDlg, IDC_CHECK_SHOW_EXPIRED,
g_ShowExpiredTime ? BST_CHECKED : BST_UNCHECKED );
@@ -1575,7 +1588,7 @@ INT_PTR CALLBACK AdvancedBreakProc( HWND hDlg, UINT message, WPARAM wParam, LPAR
EnableWindow( GetDlgItem( hDlg, IDC_BACKGROUND_BROWSE ), FALSE );
EnableWindow( GetDlgItem( hDlg, IDC_CHECK_BACKGROUND_STRETCH ), FALSE );
}
- CheckDlgButton( hDlg,
+ CheckDlgButton( hDlg,
g_BreakShowDesktop ? IDC_STATIC_DESKTOP_BACKGROUND : IDC_STATIC_BACKGROUND_FILE, BST_CHECKED );
_tcscpy( newBackgroundFile, g_BreakBackgroundFile );
SetDlgItemText( hDlg, IDC_BACKGROUND_FILE, g_BreakBackgroundFile );
@@ -1585,10 +1598,10 @@ INT_PTR CALLBACK AdvancedBreakProc( HWND hDlg, UINT message, WPARAM wParam, LPAR
for( i = 10; i <= 100; i += 10) {
_stprintf( opacity, L"%d%%", i );
- SendMessage( GetDlgItem( hDlg, IDC_OPACITY ), CB_ADDSTRING, 0,
+ SendMessage( GetDlgItem( hDlg, IDC_OPACITY ), CB_ADDSTRING, 0,
reinterpret_cast(opacity));
}
- SendMessage( GetDlgItem( hDlg, IDC_OPACITY ), CB_SETCURSEL,
+ SendMessage( GetDlgItem( hDlg, IDC_OPACITY ), CB_SETCURSEL,
g_BreakOpacity / 10 - 1, 0 );
return TRUE;
@@ -1597,25 +1610,25 @@ INT_PTR CALLBACK AdvancedBreakProc( HWND hDlg, UINT message, WPARAM wParam, LPAR
case BN_CLICKED:
if( LOWORD( wParam ) == IDC_CHECK_SOUND_FILE ) {
- EnableWindow( GetDlgItem( hDlg, IDC_STATIC_SOUND_FILE ),
+ EnableWindow( GetDlgItem( hDlg, IDC_STATIC_SOUND_FILE ),
IsDlgButtonChecked( hDlg, IDC_CHECK_SOUND_FILE) == BST_CHECKED );
- EnableWindow( GetDlgItem( hDlg, IDC_SOUND_FILE ),
+ EnableWindow( GetDlgItem( hDlg, IDC_SOUND_FILE ),
+ IsDlgButtonChecked( hDlg, IDC_CHECK_SOUND_FILE) == BST_CHECKED );
+ EnableWindow( GetDlgItem( hDlg, IDC_SOUND_BROWSE ),
IsDlgButtonChecked( hDlg, IDC_CHECK_SOUND_FILE) == BST_CHECKED );
- EnableWindow( GetDlgItem( hDlg, IDC_SOUND_BROWSE ),
- IsDlgButtonChecked( hDlg, IDC_CHECK_SOUND_FILE) == BST_CHECKED );
}
if( LOWORD( wParam ) == IDC_CHECK_BACKGROUND_FILE ) {
- EnableWindow( GetDlgItem( hDlg, IDC_CHECK_BACKGROUND_STRETCH ),
+ EnableWindow( GetDlgItem( hDlg, IDC_CHECK_BACKGROUND_STRETCH ),
IsDlgButtonChecked( hDlg, IDC_CHECK_BACKGROUND_FILE) == BST_CHECKED );
- EnableWindow( GetDlgItem( hDlg, IDC_STATIC_DESKTOP_BACKGROUND ),
+ EnableWindow( GetDlgItem( hDlg, IDC_STATIC_DESKTOP_BACKGROUND ),
IsDlgButtonChecked( hDlg, IDC_CHECK_BACKGROUND_FILE) == BST_CHECKED );
- EnableWindow( GetDlgItem( hDlg, IDC_STATIC_BACKGROUND_FILE ),
+ EnableWindow( GetDlgItem( hDlg, IDC_STATIC_BACKGROUND_FILE ),
IsDlgButtonChecked( hDlg, IDC_CHECK_BACKGROUND_FILE) == BST_CHECKED );
- EnableWindow( GetDlgItem( hDlg, IDC_BACKGROUND_FILE ),
+ EnableWindow( GetDlgItem( hDlg, IDC_BACKGROUND_FILE ),
+ IsDlgButtonChecked( hDlg, IDC_CHECK_BACKGROUND_FILE) == BST_CHECKED );
+ EnableWindow( GetDlgItem( hDlg, IDC_BACKGROUND_BROWSE ),
IsDlgButtonChecked( hDlg, IDC_CHECK_BACKGROUND_FILE) == BST_CHECKED );
- EnableWindow( GetDlgItem( hDlg, IDC_BACKGROUND_BROWSE ),
- IsDlgButtonChecked( hDlg, IDC_CHECK_BACKGROUND_FILE) == BST_CHECKED );
}
break;
}
@@ -1704,7 +1717,7 @@ INT_PTR CALLBACK AdvancedBreakProc( HWND hDlg, UINT message, WPARAM wParam, LPAR
#endif
if( g_BreakPlaySoundFile && GetFileAttributes( newSoundFile ) == -1 ) {
- MessageBox( hDlg, L"The specified sound file is inaccessible",
+ MessageBox( hDlg, L"The specified sound file is inaccessible",
L"Advanced Break Options Error", MB_ICONERROR );
break;
}
@@ -1715,7 +1728,7 @@ INT_PTR CALLBACK AdvancedBreakProc( HWND hDlg, UINT message, WPARAM wParam, LPAR
if( !g_BreakShowDesktop && g_BreakShowBackgroundFile && GetFileAttributes( newBackgroundFile ) == -1 ) {
- MessageBox( hDlg, L"The specified background file is inaccessible",
+ MessageBox( hDlg, L"The specified background file is inaccessible",
L"Advanced Break Options Error", MB_ICONERROR );
break;
}
@@ -1729,7 +1742,7 @@ INT_PTR CALLBACK AdvancedBreakProc( HWND hDlg, UINT message, WPARAM wParam, LPAR
break;
}
}
- GetDlgItemText( hDlg, IDC_OPACITY, opacity, sizeof(opacity)/sizeof(opacity[0]));
+ GetDlgItemText( hDlg, IDC_OPACITY, opacity, sizeof(opacity)/sizeof(opacity[0]));
_stscanf( opacity, L"%d%%", &g_BreakOpacity );
reg.WriteRegSettings( RegSettings );
EndDialog(hDlg, 0);
@@ -1753,14 +1766,14 @@ INT_PTR CALLBACK AdvancedBreakProc( HWND hDlg, UINT message, WPARAM wParam, LPAR
// OptionsTabProc
//
//----------------------------------------------------------------------------
-INT_PTR CALLBACK OptionsTabProc( HWND hDlg, UINT message,
- WPARAM wParam, LPARAM lParam )
+INT_PTR CALLBACK OptionsTabProc( HWND hDlg, UINT message,
+ WPARAM wParam, LPARAM lParam )
{
HDC hDC;
LOGFONT lf;
CHOOSEFONT chooseFont;
HFONT hFont;
- PAINTSTRUCT ps;
+ PAINTSTRUCT ps;
HWND hTextPreview;
HDC hDc;
RECT previewRc;
@@ -1771,6 +1784,58 @@ INT_PTR CALLBACK OptionsTabProc( HWND hDlg, UINT message,
case WM_INITDIALOG:
return TRUE;
case WM_COMMAND:
+ // Handle combo box selection changes
+ if (HIWORD(wParam) == CBN_SELCHANGE) {
+ if (LOWORD(wParam) == IDC_RECORD_SCALING) {
+
+ int format = static_cast(SendMessage(GetDlgItem(hDlg, IDC_RECORD_FORMAT), CB_GETCURSEL, 0, 0));
+ int scale = static_cast(SendMessage(GetDlgItem(hDlg, IDC_RECORD_SCALING), CB_GETCURSEL, 0, 0));
+ if(format == 0)
+ {
+ g_RecordScalingGIF = static_cast((scale + 1) * 10);
+ }
+ else
+ {
+ g_RecordScalingMP4 = static_cast((scale + 1) * 10);
+ }
+ }
+ else if (LOWORD(wParam) == IDC_RECORD_FORMAT) {
+ // Get the currently selected format
+ int selection = static_cast(SendMessage(GetDlgItem(hDlg, IDC_RECORD_FORMAT),
+ CB_GETCURSEL, 0, 0));
+
+ // Get the selected text to check if it's GIF
+ TCHAR selectedText[32] = {0};
+ SendMessage(GetDlgItem(hDlg, IDC_RECORD_FORMAT),
+ CB_GETLBTEXT, selection, reinterpret_cast(selectedText));
+
+ // Check if GIF is selected by comparing the text
+ bool isGifSelected = (wcscmp(selectedText, L"GIF") == 0);
+
+ // if gif is selected set the scaling to the g_recordScaleGIF value otherwise to the g_recordScaleMP4 value
+ if (isGifSelected) {
+ g_RecordScaling = g_RecordScalingGIF;
+
+ } else {
+
+ g_RecordScaling = g_RecordScalingMP4;
+ }
+
+ for (int i = 0; i < 10; i++) {
+ int scalingValue = (i + 1) * 10;
+ if (scalingValue == static_cast(g_RecordScaling)) {
+ SendMessage(GetDlgItem(hDlg, IDC_RECORD_SCALING),
+ CB_SETCURSEL, i, 0);
+ break;
+ }
+ }
+
+ // Enable/disable microphone controls based on selection
+ EnableWindow(GetDlgItem(hDlg, IDC_MICROPHONE), !isGifSelected);
+ EnableWindow(GetDlgItem(hDlg, IDC_CAPTURE_AUDIO), !isGifSelected);
+ }
+ }
+
switch ( LOWORD( wParam )) {
case IDC_ADVANCED_BREAK:
DialogBox( g_hInstance, L"ADVANCED_BREAK", hDlg, AdvancedBreakProc );
@@ -1785,7 +1850,7 @@ INT_PTR CALLBACK OptionsTabProc( HWND hDlg, UINT message,
chooseFont.hwndOwner = hDlg;
chooseFont.lpLogFont = &lf;
chooseFont.Flags = CF_SCREENFONTS|CF_ENABLETEMPLATE|
- CF_INITTOLOGFONTSTRUCT|CF_LIMITSIZE;
+ CF_INITTOLOGFONTSTRUCT|CF_LIMITSIZE;
chooseFont.rgbColors = RGB (0, 0, 0);
chooseFont.lCustData = 0;
chooseFont.nSizeMin = 16;
@@ -1811,7 +1876,7 @@ INT_PTR CALLBACK OptionsTabProc( HWND hDlg, UINT message,
openFileName.nFilterIndex = 1;
openFileName.lpstrFilter = L"All Files\0*.*\0\0";
openFileName.lpstrFile = filePath;
-
+
if( GetOpenFileName( &openFileName ) )
{
if( GetFileAttributes( filePath ) == -1 )
@@ -1835,20 +1900,20 @@ INT_PTR CALLBACK OptionsTabProc( HWND hDlg, UINT message,
LOGFONT _lf = g_LogFont;
_lf.lfHeight = -21;
hFont = CreateFontIndirect( &_lf);
- hDc = BeginPaint(hDlg, &ps);
+ hDc = BeginPaint(hDlg, &ps);
SelectObject( hDc, hFont );
GetWindowRect( hTextPreview, &previewRc );
- MapWindowPoints( NULL, hDlg, reinterpret_cast(&previewRc), 2);
+ MapWindowPoints( NULL, hDlg, reinterpret_cast(&previewRc), 2);
previewRc.top += 6;
- DrawText( hDc, L"Sample", static_cast(_tcslen(L"Sample")), &previewRc,
+ DrawText( hDc, L"Sample", static_cast(_tcslen(L"Sample")), &previewRc,
DT_CENTER|DT_VCENTER|DT_SINGLELINE );
EndPaint( hDlg, &ps );
DeleteObject( hFont );
}
- break;
+ break;
default:
break;
}
@@ -1861,7 +1926,7 @@ INT_PTR CALLBACK OptionsTabProc( HWND hDlg, UINT message,
// OptionsAddTabs
//
//----------------------------------------------------------------------------
-VOID OptionsAddTabs( HWND hOptionsDlg, HWND hTabCtrl )
+VOID OptionsAddTabs( HWND hOptionsDlg, HWND hTabCtrl )
{
int i;
TCITEM tcItem;
@@ -1873,14 +1938,14 @@ VOID OptionsAddTabs( HWND hOptionsDlg, HWND hTabCtrl )
tcItem.mask = TCIF_TEXT;
tcItem.pszText = g_OptionsTabs[i].TabTitle;
TabCtrl_InsertItem( hTabCtrl, i, &tcItem );
- g_OptionsTabs[i].hPage = CreateDialog( g_hInstance, g_OptionsTabs[i].TabTitle,
+ g_OptionsTabs[i].hPage = CreateDialog( g_hInstance, g_OptionsTabs[i].TabTitle,
hOptionsDlg, OptionsTabProc );
}
TabCtrl_AdjustRect( hTabCtrl, FALSE, &rc );
for( i = 0; i < sizeof( g_OptionsTabs )/sizeof(g_OptionsTabs[0]); i++ ) {
pageRc = rc;
- MapWindowPoints( NULL, g_OptionsTabs[i].hPage, reinterpret_cast(&pageRc), 2);
+ MapWindowPoints( NULL, g_OptionsTabs[i].hPage, reinterpret_cast(&pageRc), 2);
SetWindowPos( g_OptionsTabs[i].hPage,
HWND_TOP,
@@ -1914,6 +1979,8 @@ void UnregisterAllHotkeys( HWND hWnd )
UnregisterHotKey( hWnd, SNIP_SAVE_HOTKEY);
UnregisterHotKey( hWnd, DEMOTYPE_HOTKEY );
UnregisterHotKey( hWnd, DEMOTYPE_RESET_HOTKEY );
+ UnregisterHotKey( hWnd, RECORD_GIF_HOTKEY );
+ UnregisterHotKey( hWnd, RECORD_GIF_WINDOW_HOTKEY );
}
//----------------------------------------------------------------------------
@@ -1943,6 +2010,9 @@ void RegisterAllHotkeys(HWND hWnd)
RegisterHotKey(hWnd, RECORD_CROP_HOTKEY, (g_RecordToggleMod ^ MOD_SHIFT) | MOD_NOREPEAT, g_RecordToggleKey & 0xFF);
RegisterHotKey(hWnd, RECORD_WINDOW_HOTKEY, (g_RecordToggleMod ^ MOD_ALT) | MOD_NOREPEAT, g_RecordToggleKey & 0xFF);
}
+ // Register CTRL+8 for GIF recording and CTRL+ALT+8 for GIF window recording
+ RegisterHotKey(hWnd, RECORD_GIF_HOTKEY, MOD_CONTROL | MOD_NOREPEAT, 568 && 0xFF);
+ RegisterHotKey(hWnd, RECORD_GIF_WINDOW_HOTKEY, MOD_CONTROL | MOD_ALT | MOD_NOREPEAT, 568 && 0xFF);
}
@@ -1996,8 +2066,8 @@ void UpdateDrawTabHeaderFont()
// OptionsProc
//
//----------------------------------------------------------------------------
-INT_PTR CALLBACK OptionsProc( HWND hDlg, UINT message,
- WPARAM wParam, LPARAM lParam )
+INT_PTR CALLBACK OptionsProc( HWND hDlg, UINT message,
+ WPARAM wParam, LPARAM lParam )
{
static HFONT hFontBold = nullptr;
PNMLINK notify = nullptr;
@@ -2026,7 +2096,7 @@ INT_PTR CALLBACK OptionsProc( HWND hDlg, UINT message,
SetForegroundWindow( hDlg );
SetActiveWindow( hDlg );
- SetWindowPos( hDlg, HWND_TOP, 0, 0, 0, 0, SWP_NOSIZE|SWP_NOMOVE|SWP_SHOWWINDOW );
+ SetWindowPos( hDlg, HWND_TOP, 0, 0, 0, 0, SWP_NOSIZE|SWP_NOMOVE|SWP_SHOWWINDOW );
#if 1
// set version info
TCHAR filePath[MAX_PATH];
@@ -2054,9 +2124,9 @@ INT_PTR CALLBACK OptionsProc( HWND hDlg, UINT message,
UpdateDrawTabHeaderFont();
// Configure options
- SendMessage( GetDlgItem( g_OptionsTabs[ZOOM_PAGE].hPage, IDC_HOTKEY), HKM_SETRULES,
- static_cast(HKCOMB_NONE), // invalid key combinations
- MAKELPARAM(HOTKEYF_ALT, 0)); // add ALT to invalid entries
+ SendMessage( GetDlgItem( g_OptionsTabs[ZOOM_PAGE].hPage, IDC_HOTKEY), HKM_SETRULES,
+ static_cast(HKCOMB_NONE), // invalid key combinations
+ MAKELPARAM(HOTKEYF_ALT, 0)); // add ALT to invalid entries
if( g_ToggleKey ) SendMessage( GetDlgItem( g_OptionsTabs[ZOOM_PAGE].hPage, IDC_HOTKEY), HKM_SETHOTKEY, g_ToggleKey, 0 );
if( pMagInitialize ) {
@@ -2074,13 +2144,13 @@ INT_PTR CALLBACK OptionsProc( HWND hDlg, UINT message,
if( g_DemoTypeToggleKey ) SendMessage( GetDlgItem( g_OptionsTabs[DEMOTYPE_PAGE].hPage, IDC_DEMOTYPE_HOTKEY ), HKM_SETHOTKEY, g_DemoTypeToggleKey, 0 );
if( g_RecordToggleKey ) SendMessage( GetDlgItem( g_OptionsTabs[RECORD_PAGE].hPage, IDC_RECORD_HOTKEY), HKM_SETHOTKEY, g_RecordToggleKey, 0 );
if( g_SnipToggleKey) SendMessage( GetDlgItem( g_OptionsTabs[SNIP_PAGE].hPage, IDC_SNIP_HOTKEY), HKM_SETHOTKEY, g_SnipToggleKey, 0 );
- CheckDlgButton( hDlg, IDC_SHOW_TRAY_ICON,
+ CheckDlgButton( hDlg, IDC_SHOW_TRAY_ICON,
g_ShowTrayIcon ? BST_CHECKED: BST_UNCHECKED );
- CheckDlgButton( hDlg, IDC_AUTOSTART,
+ CheckDlgButton( hDlg, IDC_AUTOSTART,
IsAutostartConfigured() ? BST_CHECKED: BST_UNCHECKED );
- CheckDlgButton( g_OptionsTabs[ZOOM_PAGE].hPage, IDC_ANIMATE_ZOOM,
+ CheckDlgButton( g_OptionsTabs[ZOOM_PAGE].hPage, IDC_ANIMATE_ZOOM,
g_AnimateZoom ? BST_CHECKED: BST_UNCHECKED );
- CheckDlgButton( g_OptionsTabs[ZOOM_PAGE].hPage, IDC_SMOOTH_IMAGE,
+ CheckDlgButton( g_OptionsTabs[ZOOM_PAGE].hPage, IDC_SMOOTH_IMAGE,
g_SmoothImage ? BST_CHECKED: BST_UNCHECKED );
SendMessage( GetDlgItem(g_OptionsTabs[ZOOM_PAGE].hPage, IDC_ZOOM_SLIDER), TBM_SETRANGE, false, MAKELONG(0,_countof(g_ZoomLevels)-1) );
@@ -2089,18 +2159,18 @@ INT_PTR CALLBACK OptionsProc( HWND hDlg, UINT message,
_stprintf( text, L"%d", g_PenWidth );
SetDlgItemText( g_OptionsTabs[DRAW_PAGE].hPage, IDC_PEN_WIDTH, text );
SendMessage( GetDlgItem( g_OptionsTabs[DRAW_PAGE].hPage, IDC_PEN_WIDTH ), EM_LIMITTEXT, 1, 0 );
- SendMessage (GetDlgItem( g_OptionsTabs[DRAW_PAGE].hPage, IDC_SPIN), UDM_SETRANGE, 0L,
+ SendMessage (GetDlgItem( g_OptionsTabs[DRAW_PAGE].hPage, IDC_SPIN), UDM_SETRANGE, 0L,
MAKELPARAM (19, 1));
_stprintf( text, L"%d", g_BreakTimeout );
SetDlgItemText( g_OptionsTabs[BREAK_PAGE].hPage, IDC_TIMER, text );
SendMessage( GetDlgItem( g_OptionsTabs[BREAK_PAGE].hPage, IDC_TIMER ), EM_LIMITTEXT, 2, 0 );
- SendMessage (GetDlgItem( g_OptionsTabs[BREAK_PAGE].hPage, IDC_SPIN_TIMER), UDM_SETRANGE, 0L,
+ SendMessage (GetDlgItem( g_OptionsTabs[BREAK_PAGE].hPage, IDC_SPIN_TIMER), UDM_SETRANGE, 0L,
MAKELPARAM (99, 1));
CheckDlgButton( g_OptionsTabs[BREAK_PAGE].hPage, IDC_CHECK_SHOW_EXPIRED,
g_ShowExpiredTime ? BST_CHECKED : BST_UNCHECKED );
- CheckDlgButton( g_OptionsTabs[RECORD_PAGE].hPage, IDC_CAPTURE_AUDIO,
+ CheckDlgButton( g_OptionsTabs[RECORD_PAGE].hPage, IDC_CAPTURE_AUDIO,
g_CaptureAudio ? BST_CHECKED: BST_UNCHECKED );
for (int i = 0; i < _countof(g_FramerateOptions); i++) {
@@ -2113,12 +2183,33 @@ INT_PTR CALLBACK OptionsProc( HWND hDlg, UINT message,
SendMessage(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_RECORD_FRAME_RATE), CB_SETCURSEL, static_cast(i), static_cast(0));
}
}
+
+ // Add the recording format to the combo box and set the current selection
+ size_t selection = 0;
+ const wchar_t* currentFormatString = (g_RecordingFormat == RecordingFormat::GIF) ? L"GIF" : L"MP4";
+
+ for( size_t i = 0; i < (sizeof(g_RecordingFormats) / sizeof(g_RecordingFormats[0])); i++ )
+ {
+ SendMessage( GetDlgItem( g_OptionsTabs[RECORD_PAGE].hPage, IDC_RECORD_FORMAT ), static_cast(CB_ADDSTRING), static_cast(0), reinterpret_cast(g_RecordingFormats[i]) );
+
+ if( selection == 0 && wcscmp( g_RecordingFormats[i], currentFormatString ) == 0 )
+ {
+ selection = i;
+ }
+ }
+ SendMessage( GetDlgItem( g_OptionsTabs[RECORD_PAGE].hPage, IDC_RECORD_FORMAT ), CB_SETCURSEL, static_cast(selection), static_cast(0) );
+
for(unsigned int i = 1; i < 11; i++) {
_stprintf(text, L"%2.1f", (static_cast(i)) / 10 );
SendMessage(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_RECORD_SCALING), static_cast(CB_ADDSTRING),
static_cast(0), reinterpret_cast(text));
- if (g_RecordScaling == i*10 ) {
+
+ if (g_RecordingFormat == RecordingFormat::GIF && i*10 == g_RecordScalingGIF ) {
+
+ SendMessage(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_RECORD_SCALING), CB_SETCURSEL, static_cast(i)-1, static_cast(0));
+ }
+ if (g_RecordingFormat == RecordingFormat::MP4 && i*10 == g_RecordScalingMP4 ) {
SendMessage(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_RECORD_SCALING), CB_SETCURSEL, static_cast(i)-1, static_cast(0));
}
@@ -2136,7 +2227,7 @@ INT_PTR CALLBACK OptionsProc( HWND hDlg, UINT message,
// Add the microphone devices to the combo box and set the current selection
SendMessage( GetDlgItem( g_OptionsTabs[RECORD_PAGE].hPage, IDC_MICROPHONE ), static_cast(CB_ADDSTRING), static_cast(0), reinterpret_cast(L"Default"));
- size_t selection = 0;
+ selection = 0;
for( size_t i = 0; i < microphones.size(); i++ )
{
SendMessage( GetDlgItem( g_OptionsTabs[RECORD_PAGE].hPage, IDC_MICROPHONE ), static_cast(CB_ADDSTRING), static_cast(0), reinterpret_cast(microphones[i].second.c_str()) );
@@ -2147,6 +2238,11 @@ INT_PTR CALLBACK OptionsProc( HWND hDlg, UINT message,
}
SendMessage( GetDlgItem( g_OptionsTabs[RECORD_PAGE].hPage, IDC_MICROPHONE ), CB_SETCURSEL, static_cast(selection), static_cast(0) );
+ // Set initial state of microphone controls based on recording format
+ bool isGifSelected = (g_RecordingFormat == RecordingFormat::GIF);
+ EnableWindow(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_MICROPHONE), !isGifSelected);
+ EnableWindow(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_CAPTURE_AUDIO), !isGifSelected);
+
if( GetFileAttributes( g_DemoTypeFile ) == -1 )
{
memset( g_DemoTypeFile, 0, sizeof( g_DemoTypeFile ) );
@@ -2176,7 +2272,7 @@ INT_PTR CALLBACK OptionsProc( HWND hDlg, UINT message,
break;
case WM_CTLCOLORSTATIC:
- if( reinterpret_cast(lParam) == GetDlgItem( hDlg, IDC_TITLE ) ||
+ if( reinterpret_cast(lParam) == GetDlgItem( hDlg, IDC_TITLE ) ||
reinterpret_cast(lParam) == GetDlgItem(hDlg, IDC_DRAWING) ||
reinterpret_cast(lParam) == GetDlgItem(hDlg, IDC_ZOOM) ||
reinterpret_cast(lParam) == GetDlgItem(hDlg, IDC_BREAK) ||
@@ -2249,7 +2345,17 @@ INT_PTR CALLBACK OptionsProc( HWND hDlg, UINT message,
text[2] = 0;
newTimeout = _tstoi( text );
- g_RecordFrameRate = g_FramerateOptions[SendMessage(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_RECORD_FRAME_RATE), static_cast(CB_GETCURSEL), static_cast(0), static_cast(0))];
+ if( g_RecordingFormat == RecordingFormat::GIF )
+ {
+ // Hardcode lower frame rate for GIFs
+ g_RecordFrameRate = 15;
+ }
+ else
+ {
+ g_RecordFrameRate = g_FramerateOptions[SendMessage(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_RECORD_FRAME_RATE), static_cast(CB_GETCURSEL), static_cast(0), static_cast(0))];
+ }
+
+ g_RecordingFormat = static_cast(SendMessage(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_RECORD_FORMAT), static_cast(CB_GETCURSEL), static_cast(0), static_cast(0)));
g_RecordScaling = static_cast(SendMessage(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_RECORD_SCALING), static_cast(CB_GETCURSEL), static_cast(0), static_cast(0)) * 10 + 10);
// Get the selected microphone
@@ -2263,7 +2369,7 @@ INT_PTR CALLBACK OptionsProc( HWND hDlg, UINT message,
UnregisterAllHotkeys(GetParent( hDlg ));
break;
- } else if(newLiveZoomToggleKey &&
+ } else if(newLiveZoomToggleKey &&
(!RegisterHotKey( GetParent( hDlg ), LIVE_HOTKEY, newLiveZoomToggleMod, newLiveZoomToggleKey & 0xFF ) ||
!RegisterHotKey(GetParent(hDlg), LIVE_DRAW_HOTKEY, (newLiveZoomToggleMod ^ MOD_SHIFT), newLiveZoomToggleKey & 0xFF))) {
@@ -2286,7 +2392,7 @@ INT_PTR CALLBACK OptionsProc( HWND hDlg, UINT message,
UnregisterAllHotkeys(GetParent( hDlg ));
break;
- } else if( newDemoTypeToggleKey &&
+ } else if( newDemoTypeToggleKey &&
(!RegisterHotKey( GetParent( hDlg ), DEMOTYPE_HOTKEY, newDemoTypeToggleMod, newDemoTypeToggleKey & 0xFF ) ||
!RegisterHotKey(GetParent(hDlg), DEMOTYPE_RESET_HOTKEY, (newDemoTypeToggleMod ^ MOD_SHIFT), newDemoTypeToggleKey & 0xFF))) {
@@ -2296,7 +2402,7 @@ INT_PTR CALLBACK OptionsProc( HWND hDlg, UINT message,
break;
}
- else if (newSnipToggleKey &&
+ else if (newSnipToggleKey &&
(!RegisterHotKey(GetParent(hDlg), SNIP_HOTKEY, newSnipToggleMod, newSnipToggleKey & 0xFF) ||
!RegisterHotKey(GetParent(hDlg), SNIP_SAVE_HOTKEY, (newSnipToggleMod ^ MOD_SHIFT), newSnipToggleKey & 0xFF))) {
@@ -2305,8 +2411,8 @@ INT_PTR CALLBACK OptionsProc( HWND hDlg, UINT message,
UnregisterAllHotkeys(GetParent(hDlg));
break;
- }
- else if( newRecordToggleKey &&
+ }
+ else if( newRecordToggleKey &&
(!RegisterHotKey(GetParent(hDlg), RECORD_HOTKEY, newRecordToggleMod | MOD_NOREPEAT, newRecordToggleKey & 0xFF) ||
!RegisterHotKey(GetParent(hDlg), RECORD_CROP_HOTKEY, (newRecordToggleMod ^ MOD_SHIFT) | MOD_NOREPEAT, newRecordToggleKey & 0xFF) ||
!RegisterHotKey(GetParent(hDlg), RECORD_WINDOW_HOTKEY, (newRecordToggleMod ^ MOD_ALT) | MOD_NOREPEAT, newRecordToggleKey & 0xFF))) {
@@ -2317,7 +2423,7 @@ INT_PTR CALLBACK OptionsProc( HWND hDlg, UINT message,
break;
} else {
-
+
g_BreakTimeout = newTimeout;
g_ToggleKey = newToggleKey;
g_LiveZoomToggleKey = newLiveZoomToggleKey;
@@ -2337,7 +2443,7 @@ INT_PTR CALLBACK OptionsProc( HWND hDlg, UINT message,
hWndOptions = NULL;
EndDialog( hDlg, 0 );
- return TRUE;
+ return TRUE;
}
break;
}
@@ -2348,8 +2454,8 @@ INT_PTR CALLBACK OptionsProc( HWND hDlg, UINT message,
EndDialog( hDlg, 0 );
return TRUE;
}
- break;
-
+ break;
+
case WM_CLOSE:
hWndOptions = NULL;
RegisterAllHotkeys(GetParent(hDlg));
@@ -2388,7 +2494,7 @@ void DeleteDrawUndoList( P_DRAW_UNDO *DrawUndoList )
// PopDrawUndo
//
//----------------------------------------------------------------------------
-BOOLEAN PopDrawUndo( HDC hDc, P_DRAW_UNDO *DrawUndoList,
+BOOLEAN PopDrawUndo( HDC hDc, P_DRAW_UNDO *DrawUndoList,
int width, int height )
{
P_DRAW_UNDO nextUndo;
@@ -2396,7 +2502,7 @@ BOOLEAN PopDrawUndo( HDC hDc, P_DRAW_UNDO *DrawUndoList,
nextUndo = *DrawUndoList;
if( nextUndo ) {
- BitBlt( hDc, 0, 0, width, height,
+ BitBlt( hDc, 0, 0, width, height,
nextUndo->hDc, 0, 0, SRCCOPY|CAPTUREBLT );
*DrawUndoList = nextUndo->Next;
DeleteObject( nextUndo->hBitmap );
@@ -2444,7 +2550,7 @@ void DeleteOldestUndo( P_DRAW_UNDO *DrawUndoList )
//----------------------------------------------------------------------------
//
// GetOldestUndo
-//
+//
//----------------------------------------------------------------------------
P_DRAW_UNDO GetOldestUndo(P_DRAW_UNDO DrawUndoList)
{
@@ -2509,7 +2615,7 @@ void PushDrawUndo( HDC hDc, P_DRAW_UNDO *DrawUndoList, int width, int height )
newUndo->Next = *DrawUndoList;
*DrawUndoList = newUndo;
}
- }
+ }
}
//----------------------------------------------------------------------------
@@ -2562,8 +2668,8 @@ void ClearTypingCursor( HDC hdcScreenCompat, HDC hdcScreenCursorCompat, RECT rc,
} else {
- BitBlt(hdcScreenCompat, rc.left, rc.top, rc.right - rc.left,
- rc.bottom - rc.top, hdcScreenCursorCompat,0, 0, SRCCOPY|CAPTUREBLT );
+ BitBlt(hdcScreenCompat, rc.left, rc.top, rc.right - rc.left,
+ rc.bottom - rc.top, hdcScreenCursorCompat,0, 0, SRCCOPY|CAPTUREBLT );
}
}
@@ -2618,7 +2724,7 @@ RECT BoundMouse( float zoomLevel, MONITORINFO *monInfo, int width, int height,
int x, y;
GetZoomedTopLeftCoordinates( zoomLevel, cursorPos, &x, width, &y, height );
- rc.left = monInfo->rcMonitor.left + x;
+ rc.left = monInfo->rcMonitor.left + x;
rc.right = rc.left + static_cast(width/zoomLevel);
rc.top = monInfo->rcMonitor.top + y;
rc.bottom = rc.top + static_cast(height/zoomLevel);
@@ -2629,7 +2735,7 @@ RECT BoundMouse( float zoomLevel, MONITORINFO *monInfo, int width, int height,
rc.left, rc.top, rc.right, rc.bottom);
OutputDebug( L"mon.left: %d mon.top: %d mon.right: %d mon.bottom: %d\n",
monInfo->rcMonitor.left, monInfo->rcMonitor.top, monInfo->rcMonitor.right, monInfo->rcMonitor.bottom);
-
+
ClipCursor( &rc );
return rc;
}
@@ -2657,7 +2763,7 @@ void DrawArrow( HDC hdc, int x1, int y1, int x2, int y2, double length, double w
// get midpoint of base
int xMid = x2 - static_cast(length*dx+0.5);
int yMid = y2 - static_cast(length*dy+0.5);
-
+
// get left wing
int xLeft = xMid - static_cast(dy*width+0.5);
int yLeft = yMid + static_cast(dx*width+0.5);
@@ -2763,7 +2869,7 @@ VOID DrawShape( DWORD Shape, HDC hDc, RECT *Rect, bool UseGdiPlus = false )
case DRAW_RECTANGLE:
if (UseGdiPlus)
if(pBrush)
- DrawHighlightedShape(DRAW_RECTANGLE, hDc, pBrush, NULL,
+ DrawHighlightedShape(DRAW_RECTANGLE, hDc, pBrush, NULL,
static_cast(Rect->left - 1), static_cast(Rect->top - 1),
static_cast(Rect->right), static_cast(Rect->bottom));
else if (isBlur)
@@ -2840,7 +2946,7 @@ VOID SendPenMessage(HWND hWnd, UINT Message, LPARAM lParam)
if(GetKeyState(VK_LCONTROL) < 0 ) {
wParam |= MK_CONTROL;
- }
+ }
if( GetKeyState( VK_LSHIFT) < 0 || GetKeyState( VK_RSHIFT) < 0 ) {
wParam |= MK_SHIFT;
@@ -2853,10 +2959,10 @@ VOID SendPenMessage(HWND hWnd, UINT Message, LPARAM lParam)
//----------------------------------------------------------------------------
//
// ScalePenPosition
-//
+//
// Maps pen input to mouse input coordinates based on zoom level. Returns
// 0 if pen is active but we didn't send this message to ourselves (pen
-// signature will be missing).
+// signature will be missing).
//
//----------------------------------------------------------------------------
LPARAM ScalePenPosition( float zoomLevel, MONITORINFO *monInfo, RECT boundRc,
@@ -2867,7 +2973,7 @@ LPARAM ScalePenPosition( float zoomLevel, MONITORINFO *monInfo, RECT boundRc,
LPARAM extraInfo;
extraInfo = GetMessageExtraInfo();
- if( g_PenDown ) {
+ if( g_PenDown ) {
// ignore messages we didn't tag as pen
if (extraInfo == MI_WP_SIGNATURE) {
@@ -2890,7 +2996,7 @@ LPARAM ScalePenPosition( float zoomLevel, MONITORINFO *monInfo, RECT boundRc,
OutputDebug(L"Ignore pen message we didn't send\n");
lParam = 0;
}
-
+
} else {
if( !GetClipCursor( &rc )) {
@@ -2900,7 +3006,7 @@ LPARAM ScalePenPosition( float zoomLevel, MONITORINFO *monInfo, RECT boundRc,
OutputDebug( L"Mouse message\n");
}
return lParam;
-}
+}
//----------------------------------------------------------------------------
@@ -2927,7 +3033,7 @@ BOOLEAN DrawHighlightedCursor( float ZoomLevel, int Width, int Height )
// InvalidateCursorMoveArea
//
//----------------------------------------------------------------------------
-void InvalidateCursorMoveArea( HWND hWnd, float zoomLevel, int width, int height,
+void InvalidateCursorMoveArea( HWND hWnd, float zoomLevel, int width, int height,
POINT currentPt, POINT prevPt, POINT cursorPos )
{
int x, y;
@@ -2935,7 +3041,7 @@ void InvalidateCursorMoveArea( HWND hWnd, float zoomLevel, int width, int height
int invWidth = g_PenWidth + CURSOR_SAVE_MARGIN;
if( DrawHighlightedCursor( zoomLevel, width, height ) ) {
-
+
invWidth = g_PenWidth * 3 + 1;
}
GetZoomedTopLeftCoordinates( zoomLevel, &cursorPos, &x, width, &y, height );
@@ -3058,7 +3164,7 @@ void DrawCursor( HDC hDcTarget, POINT pt, float ZoomLevel, int Width, int Height
//
//----------------------------------------------------------------------------
void ResizePen( HWND hWnd, HDC hdcScreenCompat, HDC hdcScreenCursorCompat, POINT prevPt,
- BOOLEAN g_Tracing, BOOLEAN *g_Drawing, float g_LiveZoomLevel,
+ BOOLEAN g_Tracing, BOOLEAN *g_Drawing, float g_LiveZoomLevel,
BOOLEAN isUser, int newWidth )
{
if( !g_Tracing ) {
@@ -3117,7 +3223,7 @@ bool IsPenInverted( WPARAM wParam )
//----------------------------------------------------------------------------
//
// CaptureScreenshotAsync
-//
+//
// Captures the specified screen using the capture APIs
//
//----------------------------------------------------------------------------
@@ -3128,16 +3234,16 @@ std::future> CaptureScreenshotAsync(winrt::IDire
winrt::com_ptr d3dContext;
d3dDevice->GetImmediateContext(d3dContext.put());
- // Creating our frame pool with CreateFreeThreaded means that we
- // will be called back from the frame pool's internal worker thread
- // instead of the thread we are currently on. It also disables the
- // DispatcherQueue requirement.
- auto framePool = winrt::Direct3D11CaptureFramePool::CreateFreeThreaded(
- device,
- pixelFormat,
- 1,
- item.Size());
- auto session = framePool.CreateCaptureSession(item);
+ // Creating our frame pool with CreateFreeThreaded means that we
+ // will be called back from the frame pool's internal worker thread
+ // instead of the thread we are currently on. It also disables the
+ // DispatcherQueue requirement.
+ auto framePool = winrt::Direct3D11CaptureFramePool::CreateFreeThreaded(
+ device,
+ pixelFormat,
+ 1,
+ item.Size());
+ auto session = framePool.CreateCaptureSession(item);
wil::shared_event captureEvent(wil::EventOptions::ManualReset);
winrt::Direct3D11CaptureFrame frame{ nullptr };
@@ -3167,7 +3273,7 @@ std::future> CaptureScreenshotAsync(winrt::IDire
//----------------------------------------------------------------------------
//
// CaptureScreenshot
-//
+//
// Captures the specified screen using the capture APIs
//
//----------------------------------------------------------------------------
@@ -3196,9 +3302,9 @@ winrt::com_ptrCaptureScreenshot(winrt::DirectXPixelFormat const
//----------------------------------------------------------------------------
//
// CopyD3DTexture
-//
+//
//----------------------------------------------------------------------------
-inline auto CopyD3DTexture(winrt::com_ptr const& device,
+inline auto CopyD3DTexture(winrt::com_ptr const& device,
winrt::com_ptr const& texture, bool asStagingTexture)
{
winrt::com_ptr context;
@@ -3224,9 +3330,9 @@ inline auto CopyD3DTexture(winrt::com_ptr const& device,
//----------------------------------------------------------------------------
//
// PrepareStagingTexture
-//
+//
//----------------------------------------------------------------------------
-inline auto PrepareStagingTexture(winrt::com_ptr const& device,
+inline auto PrepareStagingTexture(winrt::com_ptr const& device,
winrt::com_ptr const& texture)
{
// If our texture is already set up for staging, then use it.
@@ -3244,7 +3350,7 @@ inline auto PrepareStagingTexture(winrt::com_ptr const& device,
//----------------------------------------------------------------------------
//
// GetBytesPerPixel
-//
+//
//----------------------------------------------------------------------------
inline size_t
GetBytesPerPixel(DXGI_FORMAT pixelFormat)
@@ -3342,7 +3448,7 @@ GetBytesPerPixel(DXGI_FORMAT pixelFormat)
//----------------------------------------------------------------------------
//
// CopyBytesFromTexture
-//
+//
//----------------------------------------------------------------------------
inline auto CopyBytesFromTexture(winrt::com_ptr const& texture, uint32_t subresource = 0)
{
@@ -3395,6 +3501,12 @@ void StopRecording()
g_RecordingSession = nullptr;
}
+ if ( g_GifRecordingSession != nullptr ) {
+
+ g_GifRecordingSession->Close();
+ g_GifRecordingSession = nullptr;
+ }
+
g_RecordToggle = FALSE;
#if WINDOWS_CURSOR_RECORDING_WORKAROUND
@@ -3442,7 +3554,7 @@ auto GetUniqueRecordingFilename()
//----------------------------------------------------------------------------
//
// StartRecordingAsync
-//
+//
// Starts the screen recording.
//
//----------------------------------------------------------------------------
@@ -3451,7 +3563,10 @@ winrt::fire_and_forget StartRecordingAsync( HWND hWnd, LPRECT rcCrop, HWND hWndR
auto tempFolderPath = std::filesystem::temp_directory_path().wstring();
auto tempFolder = co_await winrt::StorageFolder::GetFolderFromPathAsync( tempFolderPath );
auto appFolder = co_await tempFolder.CreateFolderAsync( L"ZoomIt", winrt::CreationCollisionOption::OpenIfExists );
- auto file = co_await appFolder.CreateFileAsync( L"zoomit.mp4", winrt::CreationCollisionOption::ReplaceExisting );
+
+ // Choose temp file extension based on format
+ const wchar_t* tempFileName = (g_RecordingFormat == RecordingFormat::GIF) ? L"zoomit.gif" : L"zoomit.mp4";
+ auto file = co_await appFolder.CreateFileAsync( tempFileName, winrt::CreationCollisionOption::ReplaceExisting );
// Get the device
auto d3dDevice = util::CreateD3D11Device();
@@ -3460,7 +3575,7 @@ winrt::fire_and_forget StartRecordingAsync( HWND hWnd, LPRECT rcCrop, HWND hWndR
// Get the active MONITOR capture device
HMONITOR hMon = NULL;
- POINT cursorPos = { 0, 0 };
+ POINT cursorPos = { 0, 0 };
if( pMonitorFromPoint ) {
GetCursorPos( &cursorPos );
@@ -3468,27 +3583,46 @@ winrt::fire_and_forget StartRecordingAsync( HWND hWnd, LPRECT rcCrop, HWND hWndR
}
winrt::Windows::Graphics::Capture::GraphicsCaptureItem item{ nullptr };
- if( hWndRecord )
+ if( hWndRecord )
item = util::CreateCaptureItemForWindow( hWndRecord );
else
item = util::CreateCaptureItemForMonitor( hMon );
auto stream = co_await file.OpenAsync( winrt::FileAccessMode::ReadWrite );
- g_RecordingSession = VideoRecordingSession::Create(
- g_RecordDevice,
- item,
- *rcCrop,
- g_RecordFrameRate,
- g_CaptureAudio,
- stream );
- if( g_hWndLiveZoom != NULL )
- g_RecordingSession->EnableCursorCapture( false );
+ // Create the appropriate recording session based on format
+ if (g_RecordingFormat == RecordingFormat::GIF)
+ {
+ g_GifRecordingSession = GifRecordingSession::Create(
+ g_RecordDevice,
+ item,
+ *rcCrop,
+ g_RecordFrameRate,
+ stream );
- co_await g_RecordingSession->StartAsync();
+ if( g_hWndLiveZoom != NULL )
+ g_GifRecordingSession->EnableCursorCapture( false );
- // g_RecordingSession isn't null if we're aborting a recording
- if( g_RecordingSession == nullptr ) {
+ co_await g_GifRecordingSession->StartAsync();
+ }
+ else
+ {
+ g_RecordingSession = VideoRecordingSession::Create(
+ g_RecordDevice,
+ item,
+ *rcCrop,
+ g_RecordFrameRate,
+ g_CaptureAudio,
+ stream );
+
+ if( g_hWndLiveZoom != NULL )
+ g_RecordingSession->EnableCursorCapture( false );
+
+ co_await g_RecordingSession->StartAsync();
+ }
+
+ // Check if recording was aborted
+ if( g_RecordingSession == nullptr && g_GifRecordingSession == nullptr ) {
g_bSaveInProgress = true;
@@ -3504,11 +3638,24 @@ winrt::fire_and_forget StartRecordingAsync( HWND hWnd, LPRECT rcCrop, HWND hWndR
wil::com_ptr videosItem;
if( SUCCEEDED ( SHGetKnownFolderItem( FOLDERID_Videos, KF_FLAG_DEFAULT, nullptr, IID_IShellItem, (void**) videosItem.put() ) ) )
saveDialog->SetDefaultFolder( videosItem.get() );
- saveDialog->SetDefaultExtension( L".mp4" );
- COMDLG_FILTERSPEC fileTypes[] = {
- { L"MP4 Video", L"*.mp4" }
- };
- saveDialog->SetFileTypes( _countof( fileTypes ), fileTypes );
+
+ // Set file type based on the recording format
+ if (g_RecordingFormat == RecordingFormat::GIF)
+ {
+ saveDialog->SetDefaultExtension( L".gif" );
+ COMDLG_FILTERSPEC fileTypes[] = {
+ { L"GIF Animation", L"*.gif" }
+ };
+ saveDialog->SetFileTypes( _countof( fileTypes ), fileTypes );
+ }
+ else
+ {
+ saveDialog->SetDefaultExtension( L".mp4" );
+ COMDLG_FILTERSPEC fileTypes[] = {
+ { L"MP4 Video", L"*.mp4" }
+ };
+ saveDialog->SetFileTypes( _countof( fileTypes ), fileTypes );
+ }
if( g_RecordingSaveLocation.size() == 0) {
@@ -3516,8 +3663,12 @@ winrt::fire_and_forget StartRecordingAsync( HWND hWnd, LPRECT rcCrop, HWND hWndR
wil::unique_cotaskmem_string folderPath;
if (SUCCEEDED(saveDialog->GetFolder(shellItem.put())) && SUCCEEDED(shellItem->GetDisplayName(SIGDN_FILESYSPATH, folderPath.put())))
g_RecordingSaveLocation = folderPath.get();
- g_RecordingSaveLocation = std::filesystem::path{ g_RecordingSaveLocation } /= DEFAULT_RECORDING_FILE;
}
+
+ // Always use appropriate default filename based on current format
+ std::filesystem::path currentPath{ g_RecordingSaveLocation };
+ const wchar_t* defaultFile = (g_RecordingFormat == RecordingFormat::GIF) ? DEFAULT_GIF_RECORDING_FILE : DEFAULT_RECORDING_FILE;
+ g_RecordingSaveLocation = currentPath.parent_path() / defaultFile;
auto suggestedName = GetUniqueRecordingFilename();
saveDialog->SetFileName( suggestedName.c_str() );
@@ -3566,6 +3717,7 @@ winrt::fire_and_forget StartRecordingAsync( HWND hWnd, LPRECT rcCrop, HWND hWndR
}
co_await file.DeleteAsync();
g_RecordingSession = nullptr;
+ g_GifRecordingSession = nullptr;
}
} catch( const winrt::hresult_error& error ) {
@@ -3681,9 +3833,9 @@ void ShowMainWindow(HWND hWnd, const MONITORINFO& monInfo, int width, int height
//
//----------------------------------------------------------------------------
LRESULT APIENTRY MainWndProc(
- HWND hWnd,
+ HWND hWnd,
UINT message,
- WPARAM wParam,
+ WPARAM wParam,
LPARAM lParam)
{
static int width, height;
@@ -3742,7 +3894,7 @@ LRESULT APIENTRY MainWndProc(
#endif
bool isCaptureSupported = false;
RECT rc, rc1;
- PAINTSTRUCT ps;
+ PAINTSTRUCT ps;
TCHAR timerText[16];
TCHAR negativeTimerText[16];
BOOLEAN penInverted;
@@ -3754,6 +3906,7 @@ LRESULT APIENTRY MainWndProc(
OPENFILENAME openFileName;
static TCHAR filePath[MAX_PATH] = {L"zoomit"};
NOTIFYICONDATA tNotifyIconData;
+ static DWORD64 g_TelescopingZoomLastTick = 0ull;
const auto drawAllRightJustifiedLines = [&rc]( long lineHeight, bool doPop = false ) {
rc.top = textPt.y - static_cast(g_TextBufferPreviousLines.size()) * lineHeight;
@@ -3780,19 +3933,117 @@ LRESULT APIENTRY MainWndProc(
}
};
+ const auto doTelescopingZoomTimer = [hWnd, wParam, lParam, &x, &y]( bool invalidate = true ) {
+ if( zoomTelescopeStep != 0.0f )
+ {
+ zoomLevel *= zoomTelescopeStep;
+ g_TelescopingZoomLastTick = GetTickCount64();
+ if( (zoomTelescopeStep > 1 && zoomLevel >= zoomTelescopeTarget) ||
+ (zoomTelescopeStep < 1 && zoomLevel <= zoomTelescopeTarget) )
+ {
+ zoomLevel = zoomTelescopeTarget;
+
+ g_TelescopingZoomLastTick = 0ull;
+ KillTimer( hWnd, wParam );
+ OutputDebug( L"SETCURSOR mon_left: %x mon_top: %x x: %d y: %d\n",
+ monInfo.rcMonitor.left,
+ monInfo.rcMonitor.top,
+ cursorPos.x,
+ cursorPos.y );
+ SetCursorPos( monInfo.rcMonitor.left + cursorPos.x,
+ monInfo.rcMonitor.top + cursorPos.y );
+ }
+ }
+ else
+ {
+ // Case where we didn't zoom at all
+ g_TelescopingZoomLastTick = 0ull;
+ KillTimer( hWnd, wParam );
+ }
+ if( wParam == 2 && zoomLevel == 1 )
+ {
+ g_Zoomed = FALSE;
+ if( g_ZoomOnLiveZoom )
+ {
+ GetCursorPos( &cursorPos );
+ cursorPos = ScalePointInRects( cursorPos, monInfo.rcMonitor, g_LiveZoomSourceRect );
+ SetCursorPos( cursorPos.x, cursorPos.y );
+ SendMessage( hWnd, WM_HOTKEY, LIVE_HOTKEY, 0 );
+ }
+ else if( lParam != SHALLOW_ZOOM )
+ {
+ // Figure out where final unzoomed cursor should be
+ if( g_Drawing )
+ {
+ cursorPos = prevPt;
+ }
+ OutputDebug( L"FINAL MOUSE: x: %d y: %d\n", cursorPos.x, cursorPos.y );
+ GetZoomedTopLeftCoordinates( zoomLevel, &cursorPos, &x, width, &y, height );
+ cursorPos.x = monInfo.rcMonitor.left + x + static_cast((cursorPos.x - x) * zoomLevel);
+ cursorPos.y = monInfo.rcMonitor.top + y + static_cast((cursorPos.y - y) * zoomLevel);
+ SetCursorPos( cursorPos.x, cursorPos.y );
+ }
+ if( hTargetWindow )
+ {
+ SetWindowPos( hTargetWindow, HWND_BOTTOM, rcTargetWindow.left, rcTargetWindow.top, rcTargetWindow.right - rcTargetWindow.left, rcTargetWindow.bottom - rcTargetWindow.top, 0 );
+ hTargetWindow = NULL;
+ }
+ DeleteDrawUndoList( &drawUndoList );
+
+ // Restore live zoom if we came from that mode
+ if( g_ZoomOnLiveZoom )
+ {
+ SendMessage( g_hWndLiveZoom, WM_USER_SET_ZOOM, static_cast(g_LiveZoomLevel), reinterpret_cast(&g_LiveZoomSourceRect) );
+ g_ZoomOnLiveZoom = FALSE;
+ forcePenResize = TRUE;
+ }
+
+ SetForegroundWindow( g_ActiveWindow );
+ ClipCursor( NULL );
+ g_HaveDrawn = FALSE;
+ g_TypeMode = TypeModeOff;
+ g_HaveTyped = FALSE;
+ g_Drawing = FALSE;
+ EnableDisableStickyKeys( TRUE );
+ DeleteObject( hTypingFont );
+ DeleteDC( hdcScreen );
+ DeleteDC( hdcScreenCompat );
+ DeleteDC( hdcScreenCursorCompat );
+ DeleteDC( hdcScreenSaveCompat );
+ DeleteObject( hbmpCompat );
+ DeleteObject (hbmpCursorCompat );
+ DeleteObject( hbmpDrawingCompat );
+ DeleteObject( hDrawingPen );
+
+ SetFocus( g_ActiveWindow );
+ ShowWindow( hWnd, SW_HIDE );
+ }
+ if( invalidate )
+ {
+ InvalidateRect( hWnd, NULL, FALSE );
+ }
+ };
+
switch (message) {
case WM_CREATE:
// get default font
- GetObject( GetStockObject(DEFAULT_GUI_FONT), sizeof g_LogFont, &g_LogFont );
+ GetObject( GetStockObject(DEFAULT_GUI_FONT), sizeof g_LogFont, &g_LogFont );
g_LogFont.lfWeight = FW_NORMAL;
hDc = CreateCompatibleDC( NULL );
g_LogFont.lfHeight = -MulDiv(8, GetDeviceCaps(hDc, LOGPIXELSY), 72);
DeleteDC( hDc );
reg.ReadRegSettings( RegSettings );
-
- // to support migrating from
+
+ // Set g_RecordScaling based on the current recording format
+ if (g_RecordingFormat == RecordingFormat::GIF) {
+ g_RecordScaling = g_RecordScalingGIF;
+ } else {
+ g_RecordScaling = g_RecordScalingMP4;
+ }
+
+ // to support migrating from
if ((g_PenColor >> 24) == 0) {
g_PenColor |= 0xFF << 24;
}
@@ -3822,7 +4073,7 @@ LRESULT APIENTRY MainWndProc(
APPNAME, MB_ICONERROR );
showOptions = TRUE;
- } else if( g_LiveZoomToggleKey &&
+ } else if( g_LiveZoomToggleKey &&
(!RegisterHotKey( hWnd, LIVE_HOTKEY, g_LiveZoomToggleMod, g_LiveZoomToggleKey & 0xFF) ||
!RegisterHotKey(hWnd, LIVE_DRAW_HOTKEY, (g_LiveZoomToggleMod ^ MOD_SHIFT), g_LiveZoomToggleKey & 0xFF))) {
@@ -3844,7 +4095,7 @@ LRESULT APIENTRY MainWndProc(
showOptions = TRUE;
}
- else if( g_DemoTypeToggleKey &&
+ else if( g_DemoTypeToggleKey &&
(!RegisterHotKey( hWnd, DEMOTYPE_HOTKEY, g_DemoTypeToggleMod, g_DemoTypeToggleKey & 0xFF ) ||
!RegisterHotKey(hWnd, DEMOTYPE_RESET_HOTKEY, (g_DemoTypeToggleMod ^ MOD_SHIFT), g_DemoTypeToggleKey & 0xFF))) {
@@ -3853,7 +4104,7 @@ LRESULT APIENTRY MainWndProc(
showOptions = TRUE;
}
- else if (g_SnipToggleKey &&
+ else if (g_SnipToggleKey &&
(!RegisterHotKey(hWnd, SNIP_HOTKEY, g_SnipToggleMod, g_SnipToggleKey & 0xFF) ||
!RegisterHotKey(hWnd, SNIP_SAVE_HOTKEY, (g_SnipToggleMod ^ MOD_SHIFT), g_SnipToggleKey & 0xFF))) {
@@ -3862,7 +4113,7 @@ LRESULT APIENTRY MainWndProc(
showOptions = TRUE;
}
- else if (g_RecordToggleKey &&
+ else if (g_RecordToggleKey &&
(!RegisterHotKey(hWnd, RECORD_HOTKEY, g_RecordToggleMod | MOD_NOREPEAT, g_RecordToggleKey & 0xFF) ||
!RegisterHotKey(hWnd, RECORD_CROP_HOTKEY, (g_RecordToggleMod ^ MOD_SHIFT) | MOD_NOREPEAT, g_RecordToggleKey & 0xFF) ||
!RegisterHotKey(hWnd, RECORD_WINDOW_HOTKEY, (g_RecordToggleMod ^ MOD_ALT) | MOD_NOREPEAT, g_RecordToggleKey & 0xFF))) {
@@ -3985,7 +4236,7 @@ LRESULT APIENTRY MainWndProc(
// Highlight is not supported in LiveDraw
g_PenColor |= 0xFF << 24;
}
- }
+ }
break;
case SNIP_SAVE_HOTKEY:
@@ -4156,7 +4407,7 @@ LRESULT APIENTRY MainWndProc(
if( g_hWndLiveZoom == NULL ) {
OutputDebug(L"Create LIVEZOOM\n");
g_hWndLiveZoom = CreateWindowEx( WS_EX_TOOLWINDOW | WS_EX_LAYERED | WS_EX_TRANSPARENT,
- L"MagnifierClass", L"ZoomIt Live Zoom",
+ L"MagnifierClass", L"ZoomIt Live Zoom",
WS_POPUP | WS_CLIPSIBLINGS,
0, 0, 0, 0, NULL, NULL, g_hInstance, static_cast(GetForegroundWindow()) );
pSetLayeredWindowAttributes( hWnd, 0, 0, LWA_ALPHA );
@@ -4179,10 +4430,10 @@ LRESULT APIENTRY MainWndProc(
g_LiveZoomLevel = g_ZoomLevels[g_SliderZoomLevel];
#endif
// Unzoom
- SendMessage( g_hWndLiveZoom, WM_KEYDOWN, VK_ESCAPE, 0 );
+ SendMessage( g_hWndLiveZoom, WM_KEYDOWN, VK_ESCAPE, 0 );
} else {
-
+
OutputDebug(L"Show liveZoom\n");
ShowWindow( g_hWndLiveZoom, SW_SHOW );
}
@@ -4206,6 +4457,8 @@ LRESULT APIENTRY MainWndProc(
case RECORD_HOTKEY:
case RECORD_CROP_HOTKEY:
case RECORD_WINDOW_HOTKEY:
+ case RECORD_GIF_HOTKEY:
+ case RECORD_GIF_WINDOW_HOTKEY:
//
// Recording
@@ -4221,7 +4474,7 @@ LRESULT APIENTRY MainWndProc(
if( g_RecordCropping == TRUE )
{
break;
- }
+ }
// Start screen recording
try
@@ -4243,7 +4496,7 @@ LRESULT APIENTRY MainWndProc(
{
// Already recording
break;
- }
+ }
g_RecordCropping = TRUE;
@@ -4334,8 +4587,8 @@ LRESULT APIENTRY MainWndProc(
{
cropRc = {};
- // if we're recording a window, get the window
- if (wParam == RECORD_WINDOW_HOTKEY)
+ // if we're recording a window, get the window
+ if (wParam == RECORD_WINDOW_HOTKEY || wParam == RECORD_GIF_WINDOW_HOTKEY)
{
GetCursorPos(&cursorPos);
hWndRecord = WindowFromPoint(cursorPos);
@@ -4353,6 +4606,7 @@ LRESULT APIENTRY MainWndProc(
if( g_RecordToggle == FALSE )
{
g_RecordToggle = TRUE;
+
#ifdef __ZOOMIT_POWERTOYS__
if( g_StartedByPowerToys )
{
@@ -4379,7 +4633,7 @@ LRESULT APIENTRY MainWndProc(
break;
}
-
+
OutputDebug( L"ZOOM HOTKEY: %d\n", lParam);
if( g_TimerActive ) {
@@ -4444,9 +4698,9 @@ LRESULT APIENTRY MainWndProc(
// Get screen DCs
hdcScreen = CreateDC(L"DISPLAY", static_cast(NULL),
static_cast(NULL), static_cast(NULL));
- hdcScreenCompat = CreateCompatibleDC(hdcScreen);
- hdcScreenSaveCompat = CreateCompatibleDC(hdcScreen);
- hdcScreenCursorCompat = CreateCompatibleDC(hdcScreen);
+ hdcScreenCompat = CreateCompatibleDC(hdcScreen);
+ hdcScreenSaveCompat = CreateCompatibleDC(hdcScreen);
+ hdcScreenCursorCompat = CreateCompatibleDC(hdcScreen);
// Determine what monitor we're on
GetCursorPos(&cursorPos);
@@ -4461,13 +4715,13 @@ LRESULT APIENTRY MainWndProc(
bmp.bmPlanes = static_cast(GetDeviceCaps(hdcScreen, PLANES));
bmp.bmWidth = width;
bmp.bmHeight = height;
- bmp.bmWidthBytes = ((bmp.bmWidth + 15) &~15)/8;
- hbmpCompat = CreateBitmap(bmp.bmWidth, bmp.bmHeight,
+ bmp.bmWidthBytes = ((bmp.bmWidth + 15) &~15)/8;
+ hbmpCompat = CreateBitmap(bmp.bmWidth, bmp.bmHeight,
bmp.bmPlanes, bmp.bmBitsPixel, static_cast(NULL));
- SelectObject(hdcScreenCompat, hbmpCompat);
+ SelectObject(hdcScreenCompat, hbmpCompat);
// Create saved bitmap
- hbmpDrawingCompat = CreateBitmap(bmp.bmWidth, bmp.bmHeight,
+ hbmpDrawingCompat = CreateBitmap(bmp.bmWidth, bmp.bmHeight,
bmp.bmPlanes, bmp.bmBitsPixel, static_cast(NULL));
SelectObject(hdcScreenSaveCompat, hbmpDrawingCompat);
@@ -4542,7 +4796,7 @@ LRESULT APIENTRY MainWndProc(
g_TypeMode = TypeModeOff;
g_HaveDrawn = FALSE;
EnableDisableStickyKeys( TRUE );
-
+
// Go full screen
g_ActiveWindow = GetForegroundWindow();
OutputDebug( L"active window: %x\n", PtrToLong(g_ActiveWindow) );
@@ -4552,7 +4806,7 @@ LRESULT APIENTRY MainWndProc(
OutputDebug(L"Calling ShowMainWindow\n");
ShowMainWindow(hWnd, monInfo, width, height);
}
-
+
// Start telescoping zoom. Lparam is non-zero if this
// was a real hotkey and not the message we send ourself to enter
// unzoomed drawing mode.
@@ -4570,7 +4824,7 @@ LRESULT APIENTRY MainWndProc(
OutputDebug(L"Enter liveZoom draw\n");
g_LiveZoomSourceRect = *reinterpret_cast(SendMessage( g_hWndLiveZoom, WM_USER_GET_SOURCE_RECT, 0, 0 ));
g_LiveZoomLevel = *reinterpret_cast(SendMessage(g_hWndLiveZoom, WM_USER_GET_ZOOM_LEVEL, 0, 0));
-
+
// Set live zoom level to 1 in preparation of us being full screen static
zoomLevel = 1.0;
zoomTelescopeTarget = 1.0;
@@ -4613,8 +4867,11 @@ LRESULT APIENTRY MainWndProc(
zoomTelescopeStep = ZOOM_LEVEL_STEP_IN;
zoomTelescopeTarget = g_ZoomLevels[g_SliderZoomLevel];
- if( g_AnimateZoom )
- zoomLevel = static_cast(1.0) * zoomTelescopeStep;
+ if( g_AnimateZoom )
+ {
+ zoomLevel = static_cast(1.0) * zoomTelescopeStep;
+ g_TelescopingZoomLastTick = GetTickCount64();
+ }
else
zoomLevel = zoomTelescopeTarget;
SetTimer( hWnd, 1, ZOOM_LEVEL_STEP_TIME, NULL );
@@ -4629,9 +4886,10 @@ LRESULT APIENTRY MainWndProc(
if( lParam != SHALLOW_DESTROY && !g_ZoomOnLiveZoom && g_AnimateZoom &&
g_TelescopeZoomOut && zoomTelescopeTarget != 1 ) {
- // Start telescoping zoom.
+ // Start telescoping zoom.
zoomTelescopeStep = ZOOM_LEVEL_STEP_OUT;
zoomTelescopeTarget = 1.0;
+ g_TelescopingZoomLastTick = GetTickCount64();
SetTimer( hWnd, 2, ZOOM_LEVEL_STEP_TIME, NULL );
} else {
@@ -4682,7 +4940,7 @@ LRESULT APIENTRY MainWndProc(
}
break;
- case WM_POINTERDOWN:
+ case WM_POINTERDOWN:
OutputDebug(L"WM_POINTERDOWN\n");
penInverted = IsPenInverted(wParam);
if (!penInverted) {
@@ -4719,15 +4977,15 @@ LRESULT APIENTRY MainWndProc(
//
// Zoom or modify break timer
//
- if( GET_WHEEL_DELTA_WPARAM(wParam) < 0 )
+ if( GET_WHEEL_DELTA_WPARAM(wParam) < 0 )
wParam -= (WHEEL_DELTA-1) << 16;
- else
+ else
wParam += (WHEEL_DELTA-1) << 16;
delta = GET_WHEEL_DELTA_WPARAM(wParam)/WHEEL_DELTA;
- OutputDebug( L"mousewheel: wParam: %d delta: %d\n",
+ OutputDebug( L"mousewheel: wParam: %d delta: %d\n",
GET_WHEEL_DELTA_WPARAM(wParam), delta );
if( g_Zoomed ) {
-
+
if( g_TypeMode == TypeModeOff ) {
if( g_Drawing && (LOWORD( wParam ) & MK_CONTROL) ) {
@@ -4746,7 +5004,7 @@ LRESULT APIENTRY MainWndProc(
while( delta-- ) {
if( zoomIn ) {
-
+
if( zoomTelescopeTarget < ZOOM_LEVEL_MAX ) {
if( zoomTelescopeTarget < 2 ) {
@@ -4754,17 +5012,17 @@ LRESULT APIENTRY MainWndProc(
zoomTelescopeTarget = 2;
} else {
-
+
// Start telescoping zoom
- zoomTelescopeTarget = zoomTelescopeTarget * 2;
+ zoomTelescopeTarget = zoomTelescopeTarget * 2;
}
- zoomTelescopeStep = ZOOM_LEVEL_STEP_IN;
- if( g_AnimateZoom )
- zoomLevel *= zoomTelescopeStep;
+ zoomTelescopeStep = ZOOM_LEVEL_STEP_IN;
+ if( g_AnimateZoom )
+ zoomLevel *= zoomTelescopeStep;
else
zoomLevel = zoomTelescopeTarget;
- if( zoomLevel > zoomTelescopeTarget )
+ if( zoomLevel > zoomTelescopeTarget )
zoomLevel = zoomTelescopeTarget;
else
SetTimer( hWnd, 1, ZOOM_LEVEL_STEP_TIME, NULL );
@@ -4775,17 +5033,17 @@ LRESULT APIENTRY MainWndProc(
// Let them more gradually zoom out from 2x to 1x
if( zoomTelescopeTarget <= 2 ) {
- zoomTelescopeTarget *= .75;
- if( zoomTelescopeTarget < ZOOM_LEVEL_MIN )
+ zoomTelescopeTarget *= .75;
+ if( zoomTelescopeTarget < ZOOM_LEVEL_MIN )
zoomTelescopeTarget = ZOOM_LEVEL_MIN;
} else {
- zoomTelescopeTarget = zoomTelescopeTarget/2;
+ zoomTelescopeTarget = zoomTelescopeTarget/2;
}
- zoomTelescopeStep = ZOOM_LEVEL_STEP_OUT;
- if( g_AnimateZoom )
- zoomLevel *= zoomTelescopeStep;
+ zoomTelescopeStep = ZOOM_LEVEL_STEP_OUT;
+ if( g_AnimateZoom )
+ zoomLevel *= zoomTelescopeStep;
else
zoomLevel = zoomTelescopeTarget;
@@ -4809,11 +5067,11 @@ LRESULT APIENTRY MainWndProc(
RestoreCursorArea( hdcScreenCompat, hdcScreenCursorCompat, prevPt );
}
- //SetCursorPos( monInfo.rcMonitor.left + cursorPos.x,
+ //SetCursorPos( monInfo.rcMonitor.left + cursorPos.x,
// monInfo.rcMonitor.top + cursorPos.y );
}
InvalidateRect( hWnd, NULL, FALSE );
- }
+ }
}
} else {
@@ -4827,7 +5085,7 @@ LRESULT APIENTRY MainWndProc(
// Set lParam to 0 as part of message to keyup hander
DeleteObject(hTypingFont);
g_LogFont.lfHeight = max((int)(height / zoomLevel) / g_FontScale, 12);
- if (g_LogFont.lfHeight < 20)
+ if (g_LogFont.lfHeight < 20)
g_LogFont.lfQuality = NONANTIALIASED_QUALITY;
else
g_LogFont.lfQuality = ANTIALIASED_QUALITY;
@@ -4934,7 +5192,7 @@ LRESULT APIENTRY MainWndProc(
OutputDebug(L"Entering typing mode and resetting cursor position\n");
SendMessage( hWnd, WM_LBUTTONDOWN, 0, MAKELPARAM( cursorPos.x, cursorPos.y));
- }
+ }
// Do they want to right-justify text?
OutputDebug(L"Keyup Shift: %x\n", GetAsyncKeyState(VK_SHIFT));
@@ -4964,13 +5222,13 @@ LRESULT APIENTRY MainWndProc(
g_LogFont.lfQuality = ANTIALIASED_QUALITY;
hTypingFont = CreateFontIndirect( &g_LogFont );
SelectObject( hdcScreenCompat, hTypingFont );
-
+
// If lparam == 0 that means that we sent the message as part of a font resize
if( g_Drawing && lParam != 0) {
RestoreCursorArea( hdcScreenCompat, hdcScreenCursorCompat, prevPt );
PushDrawUndo( hdcScreenCompat, &drawUndoList, width, height );
-
+
} else if( !g_Drawing ) {
textPt = cursorPos;
@@ -4985,7 +5243,7 @@ LRESULT APIENTRY MainWndProc(
case WM_KEYDOWN:
if( (g_TypeMode != TypeModeOff) && g_HaveTyped && static_cast(wParam) != VK_UP && static_cast(wParam) != VK_DOWN &&
- (isprint( static_cast(wParam)) ||
+ (isprint( static_cast(wParam)) ||
wParam == VK_RETURN || wParam == VK_DELETE || wParam == VK_BACK )) {
if( wParam == VK_RETURN ) {
@@ -5077,10 +5335,10 @@ LRESULT APIENTRY MainWndProc(
}
DrawTypingCursor( hWnd, &textPt, hdcScreenCompat, hdcScreenCursorCompat, &cursorRc );
}
- }
+ }
break;
}
- switch (wParam) {
+ switch (wParam) {
case 'R':
case 'B':
case 'Y':
@@ -5089,7 +5347,7 @@ LRESULT APIENTRY MainWndProc(
case 'X':
case 'P':
if( (g_Zoomed || g_TimerActive) && (g_TypeMode == TypeModeOff)) {
-
+
PDWORD penColor;
if( g_TimerActive )
penColor = &g_BreakPenColor;
@@ -5141,12 +5399,12 @@ LRESULT APIENTRY MainWndProc(
SelectObject( hdcScreenCompat, hDrawingPen );
if( g_Drawing ) {
- SendMessage( hWnd, WM_MOUSEMOVE, 0, MAKELPARAM( prevPt.x, prevPt.y ));
-
+ SendMessage( hWnd, WM_MOUSEMOVE, 0, MAKELPARAM( prevPt.x, prevPt.y ));
+
} else if( g_TimerActive ) {
-
- InvalidateRect( hWnd, NULL, FALSE );
-
+
+ InvalidateRect( hWnd, NULL, FALSE );
+
} else if( g_TypeMode != TypeModeOff ) {
ClearTypingCursor( hdcScreenCompat, hdcScreenCursorCompat, cursorRc, g_BlankedScreen );
@@ -5160,11 +5418,11 @@ LRESULT APIENTRY MainWndProc(
if( (GetKeyState( VK_CONTROL ) & 0x8000 ) && g_HaveDrawn && !g_Tracing ) {
if( PopDrawUndo( hdcScreenCompat, &drawUndoList, width, height )) {
-
+
if( g_Drawing ) {
SaveCursorArea( hdcScreenCursorCompat, hdcScreenCompat, prevPt );
- SendMessage( hWnd, WM_MOUSEMOVE, 0, MAKELPARAM( prevPt.x, prevPt.y ));
+ SendMessage( hWnd, WM_MOUSEMOVE, 0, MAKELPARAM( prevPt.x, prevPt.y ));
}
else {
@@ -5180,7 +5438,7 @@ LRESULT APIENTRY MainWndProc(
SetCursorPos( boundRc.left + (boundRc.right - boundRc.left)/2,
boundRc.top + (boundRc.bottom - boundRc.top)/2 );
- SendMessage( hWnd, WM_MOUSEMOVE, 0,
+ SendMessage( hWnd, WM_MOUSEMOVE, 0,
MAKELPARAM( (boundRc.right - boundRc.left)/2,
(boundRc.bottom - boundRc.top)/2 ));
}
@@ -5215,7 +5473,7 @@ LRESULT APIENTRY MainWndProc(
// Save area that's going to be occupied by new cursor position
SaveCursorArea( hdcScreenCursorCompat, hdcScreenCompat, prevPt );
- SendMessage( hWnd, WM_MOUSEMOVE, 0, MAKELPARAM( prevPt.x, prevPt.y ));
+ SendMessage( hWnd, WM_MOUSEMOVE, 0, MAKELPARAM( prevPt.x, prevPt.y ));
}
break;
@@ -5243,18 +5501,18 @@ LRESULT APIENTRY MainWndProc(
}
InvalidateRect( hWnd, NULL, FALSE );
g_BlankedScreen = FALSE;
- }
+ }
break;
case VK_UP:
- SendMessage( hWnd, WM_MOUSEWHEEL,
- MAKEWPARAM( GetAsyncKeyState( VK_LCONTROL ) != 0 || GetAsyncKeyState( VK_RCONTROL ) != 0 ?
+ SendMessage( hWnd, WM_MOUSEWHEEL,
+ MAKEWPARAM( GetAsyncKeyState( VK_LCONTROL ) != 0 || GetAsyncKeyState( VK_RCONTROL ) != 0 ?
MK_CONTROL: 0, WHEEL_DELTA), 0 );
return TRUE;
case VK_DOWN:
- SendMessage( hWnd, WM_MOUSEWHEEL,
- MAKEWPARAM( GetAsyncKeyState( VK_LCONTROL ) != 0 || GetAsyncKeyState( VK_RCONTROL ) != 0 ?
+ SendMessage( hWnd, WM_MOUSEWHEEL,
+ MAKEWPARAM( GetAsyncKeyState( VK_LCONTROL ) != 0 || GetAsyncKeyState( VK_RCONTROL ) != 0 ?
MK_CONTROL: 0, -WHEEL_DELTA), 0 );
return TRUE;
@@ -5273,13 +5531,13 @@ LRESULT APIENTRY MainWndProc(
InvalidateRect( hWnd, NULL, TRUE );
}
break;
-
- case VK_ESCAPE:
+
+ case VK_ESCAPE:
if( g_TypeMode != TypeModeOff) {
// Turn off
SendMessage( hWnd, WM_USER_TYPING_OFF, 0, 0 );
-
+
} else {
forcePenResize = TRUE;
@@ -5308,6 +5566,14 @@ LRESULT APIENTRY MainWndProc(
g_Zoomed, g_Drawing, g_Tracing);
OutputDebug(L"Window visible: %d Topmost: %d\n", IsWindowVisible(hWnd), GetWindowLong(hWnd, GWL_EXSTYLE)& WS_EX_TOPMOST);
+ if( g_Zoomed && g_TelescopingZoomLastTick != 0ull && !g_Drawing && !g_Tracing )
+ {
+ ULONG64 now = GetTickCount64();
+ if( now - g_TelescopingZoomLastTick >= ZOOM_LEVEL_STEP_TIME )
+ {
+ doTelescopingZoomTimer( false );
+ }
+ }
if( g_Zoomed && (g_TypeMode == TypeModeOff) && !g_bSaveInProgress ) {
@@ -5318,7 +5584,7 @@ LRESULT APIENTRY MainWndProc(
POINT currentPt;
// Are we in pen mode on a tablet?
- lParam = ScalePenPosition( zoomLevel, &monInfo, boundRc, message, lParam);
+ lParam = ScalePenPosition( zoomLevel, &monInfo, boundRc, message, lParam);
currentPt.x = LOWORD(lParam);
currentPt.y = HIWORD(lParam);
@@ -5330,10 +5596,10 @@ LRESULT APIENTRY MainWndProc(
} else if(g_DrawingShape) {
- SetROP2(hdcScreenCompat, R2_NOTXORPEN);
-
- // If a previous target rectangle exists, erase
- // it by drawing another rectangle on top.
+ SetROP2(hdcScreenCompat, R2_NOTXORPEN);
+
+ // If a previous target rectangle exists, erase
+ // it by drawing another rectangle on top.
if( g_rcRectangle.top != g_rcRectangle.bottom ||
g_rcRectangle.left != g_rcRectangle.right )
{
@@ -5365,12 +5631,12 @@ LRESULT APIENTRY MainWndProc(
}
}
- // Save the coordinates of the target rectangle.
+ // Save the coordinates of the target rectangle.
// Avoid invalid rectangles by ensuring that the
- // value of the left coordinate is greater than
- // that of the right, and that the value of the
- // bottom coordinate is greater than that of
- // the top.
+ // value of the left coordinate is greater than
+ // that of the right, and that the value of the
+ // bottom coordinate is greater than that of
+ // the top.
if( g_DrawingShape == DRAW_LINE ||
g_DrawingShape == DRAW_ARROW ) {
@@ -5379,36 +5645,36 @@ LRESULT APIENTRY MainWndProc(
} else {
- if ((g_RectangleAnchor.x < currentPt.x) &&
+ if ((g_RectangleAnchor.x < currentPt.x) &&
(g_RectangleAnchor.y > currentPt.y)) {
- SetRect(&g_rcRectangle, g_RectangleAnchor.x, currentPt.y,
- currentPt.x, g_RectangleAnchor.y);
+ SetRect(&g_rcRectangle, g_RectangleAnchor.x, currentPt.y,
+ currentPt.x, g_RectangleAnchor.y);
- } else if ((g_RectangleAnchor.x > currentPt.x) &&
+ } else if ((g_RectangleAnchor.x > currentPt.x) &&
(g_RectangleAnchor.y > currentPt.y )) {
- SetRect(&g_rcRectangle, currentPt.x,
- currentPt.y, g_RectangleAnchor.x,g_RectangleAnchor.y);
+ SetRect(&g_rcRectangle, currentPt.x,
+ currentPt.y, g_RectangleAnchor.x,g_RectangleAnchor.y);
- } else if ((g_RectangleAnchor.x > currentPt.x) &&
+ } else if ((g_RectangleAnchor.x > currentPt.x) &&
(g_RectangleAnchor.y < currentPt.y )) {
- SetRect(&g_rcRectangle, currentPt.x, g_RectangleAnchor.y,
- g_RectangleAnchor.x, currentPt.y );
+ SetRect(&g_rcRectangle, currentPt.x, g_RectangleAnchor.y,
+ g_RectangleAnchor.x, currentPt.y );
} else {
- SetRect(&g_rcRectangle, g_RectangleAnchor.x, g_RectangleAnchor.y,
- currentPt.x, currentPt.y );
+ SetRect(&g_rcRectangle, g_RectangleAnchor.x, g_RectangleAnchor.y,
+ currentPt.x, currentPt.y );
}
}
if (g_rcRectangle.left != g_rcRectangle.right ||
g_rcRectangle.top != g_rcRectangle.bottom) {
- // Draw the new target rectangle.
+ // Draw the new target rectangle.
DrawShape(g_DrawingShape, hdcScreenCompat, &g_rcRectangle, PEN_COLOR_HIGHLIGHT(g_PenColor));
- OutputDebug(L"SHAPE: (%d, %d) - (%d, %d)\n", g_rcRectangle.left, g_rcRectangle.top,
+ OutputDebug(L"SHAPE: (%d, %d) - (%d, %d)\n", g_rcRectangle.left, g_rcRectangle.top,
g_rcRectangle.right, g_rcRectangle.bottom);
}
@@ -5446,7 +5712,7 @@ LRESULT APIENTRY MainWndProc(
BYTE* pPixels = static_cast(lineData->Scan0);
// Create a GDI bitmap that's the size of the lineBounds rectangle
- Gdiplus::Bitmap *blurBitmap = CreateGdiplusBitmap( hdcScreenCompat, // oldestUndo->hDc,
+ Gdiplus::Bitmap *blurBitmap = CreateGdiplusBitmap( hdcScreenCompat, // oldestUndo->hDc,
lineBounds.X, lineBounds.Y, lineBounds.Width, lineBounds.Height);
// Blur it
@@ -5466,8 +5732,8 @@ LRESULT APIENTRY MainWndProc(
// Draw new cursor
DrawCursor(hdcScreenCompat, currentPt, zoomLevel, width, height);
- }
- else if(PEN_COLOR_HIGHLIGHT(g_PenColor)) {
+ }
+ else if(PEN_COLOR_HIGHLIGHT(g_PenColor)) {
OutputDebug(L"HIGHLIGHT\n");
@@ -5502,7 +5768,7 @@ LRESULT APIENTRY MainWndProc(
HDC hdcDIBOrig;
HBITMAP hDibOrigBitmap, hDibBitmap;
P_DRAW_UNDO oldestUndo = GetOldestUndo(drawUndoList);
- BYTE* pDestPixels2 = CreateBitmapMemoryDIB(hdcScreenCompat, oldestUndo->hDc, &lineBounds,
+ BYTE* pDestPixels2 = CreateBitmapMemoryDIB(hdcScreenCompat, oldestUndo->hDc, &lineBounds,
&hdcDIBOrig, &hDibBitmap, &hDibOrigBitmap);
for (int local_y = 0; local_y < lineBounds.Height; ++local_y) {
@@ -5559,7 +5825,7 @@ LRESULT APIENTRY MainWndProc(
// Restore area where cursor was previously
RestoreCursorArea( hdcScreenCompat, hdcScreenCursorCompat, prevPt );
-
+
// Save area that's going to be occupied by new cursor position
SaveCursorArea( hdcScreenCursorCompat, hdcScreenCompat, currentPt );
@@ -5608,12 +5874,12 @@ LRESULT APIENTRY MainWndProc(
#if 0
{
static int index = 0;
- OutputDebug( L"%d: foreground: %x focus: %x (hwnd: %x)\n",
+ OutputDebug( L"%d: foreground: %x focus: %x (hwnd: %x)\n",
index++, (DWORD) PtrToUlong(GetForegroundWindow()), PtrToUlong(GetFocus()), PtrToUlong(hWnd));
}
#endif
return TRUE;
-
+
case WM_LBUTTONDOWN:
g_StraightDirection = 0;
@@ -5626,10 +5892,10 @@ LRESULT APIENTRY MainWndProc(
RestoreCursorArea( hdcScreenCompat, hdcScreenCursorCompat, prevPt );
}
-
+
// don't push undo if we sent this to ourselves for a pen resize
if( wParam != -1 ) {
-
+
PushDrawUndo( hdcScreenCompat, &drawUndoList, width, height );
} else {
@@ -5658,7 +5924,7 @@ LRESULT APIENTRY MainWndProc(
if( wParam & MK_SHIFT && wParam & MK_CONTROL )
g_DrawingShape = DRAW_ARROW;
- else if( wParam & MK_CONTROL )
+ else if( wParam & MK_CONTROL )
g_DrawingShape = DRAW_RECTANGLE;
else if( wParam & MK_SHIFT )
g_DrawingShape = DRAW_LINE;
@@ -5666,8 +5932,8 @@ LRESULT APIENTRY MainWndProc(
g_DrawingShape = DRAW_ELLIPSE;
g_RectangleAnchor.x = LOWORD(lParam);
g_RectangleAnchor.y = HIWORD(lParam);
- SetRect(&g_rcRectangle, g_RectangleAnchor.x, g_RectangleAnchor.y,
- g_RectangleAnchor.x, g_RectangleAnchor.y);
+ SetRect(&g_rcRectangle, g_RectangleAnchor.x, g_RectangleAnchor.y,
+ g_RectangleAnchor.x, g_RectangleAnchor.y);
} else {
@@ -5685,10 +5951,10 @@ LRESULT APIENTRY MainWndProc(
}
g_Tracing = TRUE;
SetROP2( hdcScreenCompat, R2_COPYPEN );
- prevPt.x = LOWORD(lParam);
- prevPt.y = HIWORD(lParam);
+ prevPt.x = LOWORD(lParam);
+ prevPt.y = HIWORD(lParam);
g_HaveDrawn = TRUE;
-
+
} else {
OutputDebug(L"Tracing on\n");
@@ -5779,7 +6045,7 @@ LRESULT APIENTRY MainWndProc(
return TRUE;
case WM_LBUTTONUP:
- OutputDebug(L"LBUTTONUP: zoomed: %d drawing: %d tracing: %d\n",
+ OutputDebug(L"LBUTTONUP: zoomed: %d drawing: %d tracing: %d\n",
g_Zoomed, g_Drawing, g_Tracing);
if( g_Zoomed && g_Drawing && g_Tracing ) {
@@ -5800,12 +6066,12 @@ LRESULT APIENTRY MainWndProc(
if( g_StraightDirection == -1 ) {
adjustPos.x = prevPt.x;
-
+
} else {
adjustPos.y = prevPt.y;
}
- lParam = MAKELPARAM( adjustPos.x, adjustPos.y );
+ lParam = MAKELPARAM( adjustPos.x, adjustPos.y );
if( !g_DrawingShape ) {
@@ -5843,7 +6109,7 @@ LRESULT APIENTRY MainWndProc(
}
SaveCursorArea( hdcScreenCursorCompat, hdcScreenCompat, prevPt );
DrawCursor( hdcScreenCompat, prevPt, zoomLevel, width, height );
-
+
} else if (g_rcRectangle.top != g_rcRectangle.bottom ||
g_rcRectangle.left != g_rcRectangle.right ) {
@@ -5912,18 +6178,18 @@ LRESULT APIENTRY MainWndProc(
DeleteTypedText( &typedKeyList );
// 1 means don't reset the cursor. We get that for font resizing
- // Only move the cursor if we're drawing, because otherwise the screen moves to center
+ // Only move the cursor if we're drawing, because otherwise the screen moves to center
// on the new cursor position
if( wParam != 1 && g_Drawing ) {
prevPt.x = cursorRc.left;
prevPt.y = cursorRc.top;
- SetCursorPos( monInfo.rcMonitor.left + prevPt.x,
+ SetCursorPos( monInfo.rcMonitor.left + prevPt.x,
monInfo.rcMonitor.top + prevPt.y );
SaveCursorArea( hdcScreenCursorCompat, hdcScreenCompat, prevPt );
SendMessage( hWnd, WM_MOUSEMOVE, 0, MAKELPARAM( prevPt.x, prevPt.y ));
-
+
} else if( !g_Drawing) {
// FIX: would be nice to reset cursor so screen doesn't move
@@ -5969,12 +6235,12 @@ LRESULT APIENTRY MainWndProc(
TrackPopupMenu( hPopupMenu, 0, pt.x , pt.y, 0, hWnd, NULL );
DestroyMenu( hPopupMenu );
break;
- }
+ }
case WM_LBUTTONDBLCLK:
if( !g_TimerActive ) {
SendMessage( hWnd, WM_COMMAND, IDC_OPTIONS, 0 );
-
+
} else {
SetForegroundWindow( hWnd );
@@ -6147,6 +6413,13 @@ LRESULT APIENTRY MainWndProc(
showOptions = TRUE;
}
}
+ // Register CTRL+8 for GIF recording and CTRL+ALT+8 for GIF window recording
+ if (!RegisterHotKey(hWnd, RECORD_GIF_HOTKEY, MOD_CONTROL | MOD_NOREPEAT, '8') ||
+ !RegisterHotKey(hWnd, RECORD_GIF_WINDOW_HOTKEY, MOD_CONTROL | MOD_ALT | MOD_NOREPEAT, '8'))
+ {
+ MessageBox(hWnd, L"The specified GIF recording hotkey is already in use.\nSelect a different GIF recording hotkey.", APPNAME, MB_ICONERROR);
+ showOptions = TRUE;
+ }
if (showOptions)
{
// To open the PowerToys settings in the ZoomIt page.
@@ -6247,7 +6520,7 @@ LRESULT APIENTRY MainWndProc(
openFileName.lpstrDefExt = NULL; // "*.png";
openFileName.nFilterIndex = 1;
openFileName.lpstrFilter = L"Zoomed PNG\0*.png\0"
- //"Zoomed BMP\0*.bmp\0"
+ //"Zoomed BMP\0*.bmp\0"
"Actual size PNG\0*.png\0\0";
//"Actual size BMP\0*.bmp\0\0";
openFileName.lpstrFile = filePath;
@@ -6282,7 +6555,7 @@ LRESULT APIENTRY MainWndProc(
0,
copyWidth, copyHeight,
SRCCOPY | CAPTUREBLT );
-
+
SavePng( targetFilePath, hSaveBitmap );
}
}
@@ -6362,10 +6635,10 @@ LRESULT APIENTRY MainWndProc(
monInfo.rcMonitor.left + copyX,
monInfo.rcMonitor.top + copyY,
copyWidth, copyHeight,
- SRCCOPY|CAPTUREBLT );
+ SRCCOPY|CAPTUREBLT );
if( OpenClipboard( hWnd )) {
-
+
EmptyClipboard();
SetClipboardData( CF_BITMAP, hSaveBitmap );
CloseClipboard();
@@ -6375,7 +6648,7 @@ LRESULT APIENTRY MainWndProc(
}
break;
- case IDC_DRAW:
+ case IDC_DRAW:
PostMessage( hWnd, WM_HOTKEY, DRAW_HOTKEY, 1 );
break;
@@ -6459,10 +6732,10 @@ LRESULT APIENTRY MainWndProc(
( !g_TimerActive || wcscmp( activeBreakBackgroundFile, g_BreakBackgroundFile ) ) )
{
_tcscpy( activeBreakBackgroundFile, g_BreakBackgroundFile );
-
+
DeleteObject( g_hBackgroundBmp );
DeleteDC( g_hDcBackgroundFile );
-
+
g_hBackgroundBmp = NULL;
g_hBackgroundBmp = LoadImageFile( g_BreakBackgroundFile );
if( g_hBackgroundBmp == NULL )
@@ -6504,15 +6777,15 @@ LRESULT APIENTRY MainWndProc(
hNegativeTimerFont = CreateFontIndirect( &g_LogFont );
// Create backing bitmap
- hdcScreenCompat = CreateCompatibleDC(hdcScreen);
+ hdcScreenCompat = CreateCompatibleDC(hdcScreen);
bmp.bmBitsPixel = static_cast(GetDeviceCaps(hdcScreen, BITSPIXEL));
bmp.bmPlanes = static_cast(GetDeviceCaps(hdcScreen, PLANES));
bmp.bmWidth = width;
bmp.bmHeight = height;
- bmp.bmWidthBytes = ((bmp.bmWidth + 15) &~15)/8;
- hbmpCompat = CreateBitmap(bmp.bmWidth, bmp.bmHeight,
- bmp.bmPlanes, bmp.bmBitsPixel, static_cast(NULL));
- SelectObject(hdcScreenCompat, hbmpCompat);
+ bmp.bmWidthBytes = ((bmp.bmWidth + 15) &~15)/8;
+ hbmpCompat = CreateBitmap(bmp.bmWidth, bmp.bmHeight,
+ bmp.bmPlanes, bmp.bmBitsPixel, static_cast(NULL));
+ SelectObject(hdcScreenCompat, hbmpCompat);
SetTextColor( hdcScreenCompat, g_BreakPenColor );
SetBkMode( hdcScreenCompat, TRANSPARENT );
@@ -6527,7 +6800,7 @@ LRESULT APIENTRY MainWndProc(
BringWindowToTop( hWnd );
SetForegroundWindow( hWnd );
SetActiveWindow( hWnd );
- SetWindowPos( hWnd, HWND_NOTOPMOST, monInfo.rcMonitor.left, monInfo.rcMonitor.top,
+ SetWindowPos( hWnd, HWND_NOTOPMOST, monInfo.rcMonitor.left, monInfo.rcMonitor.top,
width, height, SWP_SHOWWINDOW );
}
break;
@@ -6535,10 +6808,10 @@ LRESULT APIENTRY MainWndProc(
case IDCANCEL:
memset( &tNotifyIconData, 0, sizeof(tNotifyIconData));
- tNotifyIconData.cbSize = sizeof(NOTIFYICONDATA);
- tNotifyIconData.hWnd = hWnd;
- tNotifyIconData.uID = 1;
- Shell_NotifyIcon(NIM_DELETE, &tNotifyIconData);
+ tNotifyIconData.cbSize = sizeof(NOTIFYICONDATA);
+ tNotifyIconData.hWnd = hWnd;
+ tNotifyIconData.uID = 1;
+ Shell_NotifyIcon(NIM_DELETE, &tNotifyIconData);
reg.WriteRegSettings( RegSettings );
if( hWndOptions )
@@ -6561,93 +6834,12 @@ LRESULT APIENTRY MainWndProc(
if( breakTimeout == 0 && g_BreakPlaySoundFile ) {
PlaySound( g_BreakSoundFile, NULL, SND_FILENAME|SND_ASYNC );
- }
+ }
break;
case 2:
case 1:
- //
- // Telescoping zoom timer
- //
- if( zoomTelescopeStep ) {
-
- zoomLevel *= zoomTelescopeStep;
- if( (zoomTelescopeStep > 1 && zoomLevel >= zoomTelescopeTarget ) ||
- (zoomTelescopeStep < 1 && zoomLevel <= zoomTelescopeTarget )) {
-
- zoomLevel = zoomTelescopeTarget;
- KillTimer( hWnd, wParam );
- OutputDebug( L"SETCURSOR mon_left: %x mon_top: %x x: %d y: %d\n",
- monInfo.rcMonitor.left, monInfo.rcMonitor.top, cursorPos.x, cursorPos.y );
- SetCursorPos( monInfo.rcMonitor.left + cursorPos.x,
- monInfo.rcMonitor.top + cursorPos.y );
- }
-
- } else {
-
- // Case where we didn't zoom at all
- KillTimer( hWnd, wParam );
- }
- if( wParam == 2 && zoomLevel == 1 ) {
-
- g_Zoomed = FALSE;
- if( g_ZoomOnLiveZoom )
- {
- GetCursorPos( &cursorPos );
- cursorPos = ScalePointInRects( cursorPos, monInfo.rcMonitor, g_LiveZoomSourceRect );
- SetCursorPos( cursorPos.x, cursorPos.y );
- SendMessage(hWnd, WM_HOTKEY, LIVE_HOTKEY, 0);
- }
- else if( lParam != SHALLOW_ZOOM )
- {
- // Figure out where final unzoomed cursor should be
- if (g_Drawing) {
- cursorPos = prevPt;
- }
- OutputDebug(L"FINAL MOUSE: x: %d y: %d\n", cursorPos.x, cursorPos.y );
- GetZoomedTopLeftCoordinates(zoomLevel, &cursorPos, &x, width, &y, height);
- cursorPos.x = monInfo.rcMonitor.left + x + static_cast((cursorPos.x - x) * zoomLevel);
- cursorPos.y = monInfo.rcMonitor.top + y + static_cast((cursorPos.y - y) * zoomLevel);
- SetCursorPos(cursorPos.x, cursorPos.y);
- }
- if( hTargetWindow ) {
-
- SetWindowPos( hTargetWindow, HWND_BOTTOM, rcTargetWindow.left, rcTargetWindow.top,
- rcTargetWindow.right - rcTargetWindow.left,
- rcTargetWindow.bottom - rcTargetWindow.top, 0 );
- hTargetWindow = NULL;
- }
- DeleteDrawUndoList( &drawUndoList );
-
- // Restore live zoom if we came from that mode
- if( g_ZoomOnLiveZoom ) {
-
- SendMessage( g_hWndLiveZoom, WM_USER_SET_ZOOM, static_cast(g_LiveZoomLevel), reinterpret_cast(&g_LiveZoomSourceRect) );
- g_ZoomOnLiveZoom = FALSE;
- forcePenResize = TRUE;
- }
-
- SetForegroundWindow( g_ActiveWindow );
- ClipCursor( NULL );
- g_HaveDrawn = FALSE;
- g_TypeMode = TypeModeOff;
- g_HaveTyped = FALSE;
- g_Drawing = FALSE;
- EnableDisableStickyKeys( TRUE );
- DeleteObject( hTypingFont );
- DeleteDC( hdcScreen );
- DeleteDC( hdcScreenCompat );
- DeleteDC( hdcScreenCursorCompat );
- DeleteDC( hdcScreenSaveCompat );
- DeleteObject( hbmpCompat );
- DeleteObject( hbmpCursorCompat );
- DeleteObject( hbmpDrawingCompat );
- DeleteObject( hDrawingPen );
-
- SetFocus( g_ActiveWindow );
- ShowWindow( hWnd, SW_HIDE );
- }
- InvalidateRect( hWnd, NULL, FALSE );
+ doTelescopingZoomTimer();
break;
case 3:
@@ -6670,7 +6862,7 @@ LRESULT APIENTRY MainWndProc(
case WM_PAINT:
- hDc = BeginPaint(hWnd, &ps);
+ hDc = BeginPaint(hWnd, &ps);
if( ( ( g_RecordCropping == FALSE ) || ( zoomLevel == 1 ) ) && g_Zoomed ) {
@@ -6680,25 +6872,25 @@ LRESULT APIENTRY MainWndProc(
#if SCALE_GDIPLUS
if ( zoomLevel >= zoomTelescopeTarget ) {
// do a high-quality render
- extern void ScaleImage( HDC hdcDst, float xDst, float yDst, float wDst, float hDst,
+ extern void ScaleImage( HDC hdcDst, float xDst, float yDst, float wDst, float hDst,
HBITMAP bmSrc, float xSrc, float ySrc, float wSrc, float hSrc );
- ScaleImage( ps.hdc,
- 0, 0,
- (float)bmp.bmWidth, (float)bmp.bmHeight,
- hbmpCompat,
- (float)x, (float)y,
- width/zoomLevel, height/zoomLevel );
+ ScaleImage( ps.hdc,
+ 0, 0,
+ (float)bmp.bmWidth, (float)bmp.bmHeight,
+ hbmpCompat,
+ (float)x, (float)y,
+ width/zoomLevel, height/zoomLevel );
} else {
// do a fast, less accurate render (but use smooth if enabled)
SetStretchBltMode( hDc, g_SmoothImage ? HALFTONE : COLORONCOLOR );
- StretchBlt( ps.hdc,
- 0, 0,
- bmp.bmWidth, bmp.bmHeight,
- hdcScreenCompat,
- x, y,
+ StretchBlt( ps.hdc,
+ 0, 0,
+ bmp.bmWidth, bmp.bmHeight,
+ hdcScreenCompat,
+ x, y,
(int) (width/zoomLevel), (int) (height/zoomLevel),
- SRCCOPY);
+ SRCCOPY);
}
#else
#if SCALE_HALFTONE
@@ -6711,13 +6903,13 @@ LRESULT APIENTRY MainWndProc(
SetStretchBltMode( hDc, COLORONCOLOR );
}
#endif
- StretchBlt( ps.hdc,
- 0, 0,
- bmp.bmWidth, bmp.bmHeight,
- hdcScreenCompat,
- x, y,
+ StretchBlt( ps.hdc,
+ 0, 0,
+ bmp.bmWidth, bmp.bmHeight,
+ hdcScreenCompat,
+ x, y,
static_cast(width/zoomLevel), static_cast(height/zoomLevel),
- SRCCOPY|CAPTUREBLT );
+ SRCCOPY|CAPTUREBLT );
#endif
} else if( g_TimerActive ) {
@@ -6737,7 +6929,7 @@ LRESULT APIENTRY MainWndProc(
StretchBlt( hdcScreenCompat, 0, 0, width, height,
g_hDcBackgroundFile, 0, 0, local_bmp.bmWidth, local_bmp.bmHeight, SRCCOPY|CAPTUREBLT );
} else {
- BitBlt( hdcScreenCompat, width/2 - local_bmp.bmWidth/2, height/2 - local_bmp.bmHeight/2,
+ BitBlt( hdcScreenCompat, width/2 - local_bmp.bmWidth/2, height/2 - local_bmp.bmHeight/2,
local_bmp.bmWidth, local_bmp.bmHeight, g_hDcBackgroundFile, 0, 0, SRCCOPY|CAPTUREBLT );
}
}
@@ -6746,13 +6938,13 @@ LRESULT APIENTRY MainWndProc(
if( breakTimeout > 0 ) {
_stprintf( timerText, L"% 2d:%02d", breakTimeout/60, breakTimeout % 60 );
-
+
} else {
_tcscpy( timerText, L"0:00" );
}
rc.left = rc.top = 0;
- DrawText( hdcScreenCompat, timerText, -1, &rc,
+ DrawText( hdcScreenCompat, timerText, -1, &rc,
DT_NOCLIP|DT_LEFT|DT_NOPREFIX|DT_CALCRECT );
rc1.left = rc1.right = rc1.bottom = rc1.top = 0;
@@ -6761,7 +6953,7 @@ LRESULT APIENTRY MainWndProc(
_stprintf( negativeTimerText, L"(-% 2d:%02d)",
-breakTimeout/60, -breakTimeout % 60 );
HFONT prevFont = static_cast(SelectObject( hdcScreenCompat, hNegativeTimerFont ));
- DrawText( hdcScreenCompat, negativeTimerText, -1, &rc1,
+ DrawText( hdcScreenCompat, negativeTimerText, -1, &rc1,
DT_NOCLIP|DT_LEFT|DT_NOPREFIX|DT_CALCRECT );
SelectObject( hdcScreenCompat, prevFont );
}
@@ -6813,7 +7005,7 @@ LRESULT APIENTRY MainWndProc(
rc1.top = rc.bottom + 10;
rc1.left = rc.left + ((rc.right - rc.left)-(rc1.right-rc1.left))/2;
HFONT prevFont = static_cast(SelectObject( hdcScreenCompat, hNegativeTimerFont ));
- DrawText( hdcScreenCompat, negativeTimerText, -1, &rc1,
+ DrawText( hdcScreenCompat, negativeTimerText, -1, &rc1,
DT_NOCLIP|DT_LEFT|DT_NOPREFIX );
SelectObject( hdcScreenCompat, prevFont );
}
@@ -6821,7 +7013,7 @@ LRESULT APIENTRY MainWndProc(
// Copy to screen
BitBlt( ps.hdc, 0, 0, width, height, hdcScreenCompat, 0, 0, SRCCOPY|CAPTUREBLT );
}
- EndPaint(hWnd, &ps);
+ EndPaint(hWnd, &ps);
return TRUE;
case WM_DESTROY:
@@ -6906,7 +7098,7 @@ LRESULT CALLBACK LiveZoomWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM
if( !startedInPresentationMode ) {
SetTimer( hWnd, 1, LIVEZOOM_WINDOW_TIMEOUT, NULL );
- }
+ }
}
break;
@@ -6934,7 +7126,7 @@ LRESULT CALLBACK LiveZoomWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM
UpdateWindow(hWnd);
}
- // Are we coming back from a static zoom that
+ // Are we coming back from a static zoom that
// was started while we were live zoomed?
if( g_ZoomOnLiveZoom ) {
@@ -6974,7 +7166,7 @@ LRESULT CALLBACK LiveZoomWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM
SendMessage( hWnd, WM_TIMER, 0, 0);
SetTimer( hWnd, 0, ZOOM_LEVEL_STEP_TIME, NULL );
-
+
} else {
KillTimer( hWnd, 0 );
@@ -7012,14 +7204,14 @@ LRESULT CALLBACK LiveZoomWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM
GetCursorPos(&cursorPos);
- // Reclaim topmost status, to prevent unmagnified menus from remaining in view.
+ // Reclaim topmost status, to prevent unmagnified menus from remaining in view.
memset(&matrix, 0, sizeof(matrix));
if( !g_fullScreenWorkaround ) {
pSetLayeredWindowAttributes( hWnd, 0, 255, LWA_ALPHA );
SetWindowPos(hWnd, HWND_TOPMOST, 0, 0, 0, 0,
SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE);
-
+
OutputDebug(L"LIVEZOOM RECLAIM\n");
}
@@ -7028,7 +7220,7 @@ LRESULT CALLBACK LiveZoomWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM
moveWidth = sourceRectWidth/LIVEZOOM_MOVE_REGIONS;
moveHeight = sourceRectHeight/LIVEZOOM_MOVE_REGIONS;
curTickCount = GetTickCount();
- if( zoomLevel != zoomTelescopeTarget &&
+ if( zoomLevel != zoomTelescopeTarget &&
(prevZoomStepTickCount == 0 || (curTickCount - prevZoomStepTickCount > ZOOM_LEVEL_STEP_TIME)) ) {
prevZoomStepTickCount = curTickCount;
@@ -7040,7 +7232,7 @@ LRESULT CALLBACK LiveZoomWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM
} else {
zoomLevel *= zoomTelescopeStep;
- }
+ }
// Time to exit zoom mode?
if( zoomTelescopeTarget == 1 && zoomLevel == 1 ) {
@@ -7059,13 +7251,13 @@ LRESULT CALLBACK LiveZoomWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM
matrix.v[1][2] = (static_cast(-lastSourceRect.top) * zoomLevel );
matrix.v[2][2] = 1.0f;
}
-
+
//
// Pre-adjust for monitor boundary
//
adjustedCursorPos.x = cursorPos.x - monInfo.rcMonitor.left;
adjustedCursorPos.y = cursorPos.y - monInfo.rcMonitor.top;
- GetZoomedTopLeftCoordinates( zoomLevel, &adjustedCursorPos, reinterpret_cast(&zoomCenterPos.x), width,
+ GetZoomedTopLeftCoordinates( zoomLevel, &adjustedCursorPos, reinterpret_cast(&zoomCenterPos.x), width,
reinterpret_cast(&zoomCenterPos.y), height );
//
@@ -7078,11 +7270,11 @@ LRESULT CALLBACK LiveZoomWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM
int xOffset = cursorPos.x - lastSourceRect.left;
int yOffset = cursorPos.y - lastSourceRect.top;
- zoomCenterPos.x = 0;
- zoomCenterPos.y = 0;
- if( xOffset < moveWidth )
+ zoomCenterPos.x = 0;
+ zoomCenterPos.y = 0;
+ if( xOffset < moveWidth )
zoomCenterPos.x = lastSourceRect.left + sourceRectWidth/2 - (moveWidth - xOffset);
- else if( xOffset > moveWidth * (LIVEZOOM_MOVE_REGIONS-1) )
+ else if( xOffset > moveWidth * (LIVEZOOM_MOVE_REGIONS-1) )
zoomCenterPos.x = lastSourceRect.left + sourceRectWidth/2 + (xOffset - moveWidth*(LIVEZOOM_MOVE_REGIONS-1));
if( yOffset < moveHeight )
zoomCenterPos.y = lastSourceRect.top + sourceRectHeight/2 - (moveHeight - yOffset);
@@ -7090,8 +7282,8 @@ LRESULT CALLBACK LiveZoomWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM
zoomCenterPos.y = lastSourceRect.top + sourceRectHeight/2 + (yOffset - moveHeight*(LIVEZOOM_MOVE_REGIONS-1));
}
if( matrix.v[0][0] || zoomCenterPos.x || zoomCenterPos.y ) {
-
- if( zoomCenterPos.y == 0 )
+
+ if( zoomCenterPos.y == 0 )
zoomCenterPos.y = lastSourceRect.top + sourceRectHeight/2;
if( zoomCenterPos.x == 0 )
zoomCenterPos.x = lastSourceRect.left + sourceRectWidth/2;
@@ -7102,14 +7294,14 @@ LRESULT CALLBACK LiveZoomWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM
sourceRect.top = zoomCenterPos.y - zoomHeight / 2;
// Don't scroll outside desktop area.
- if (sourceRect.left < monInfo.rcMonitor.left)
+ if (sourceRect.left < monInfo.rcMonitor.left)
sourceRect.left = monInfo.rcMonitor.left;
else if (sourceRect.left > monInfo.rcMonitor.right - zoomWidth )
sourceRect.left = monInfo.rcMonitor.right - zoomWidth;
sourceRect.right = sourceRect.left + zoomWidth;
- if (sourceRect.top < monInfo.rcMonitor.top)
+ if (sourceRect.top < monInfo.rcMonitor.top)
sourceRect.top = monInfo.rcMonitor.top;
- else if (sourceRect.top > monInfo.rcMonitor.bottom - zoomHeight)
+ else if (sourceRect.top > monInfo.rcMonitor.bottom - zoomHeight)
sourceRect.top = monInfo.rcMonitor.bottom - zoomHeight;
sourceRect.bottom = sourceRect.top + zoomHeight;
@@ -7194,7 +7386,7 @@ LRESULT CALLBACK LiveZoomWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM
DestroyWindow( hWnd );
}
- }
+ }
}
break;
}
@@ -7207,9 +7399,9 @@ LRESULT CALLBACK LiveZoomWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM
// Existing presentation mode
DestroyWindow( hWnd );
-
+
} else if( !startedInPresentationMode && IsPresentationMode()) {
-
+
// Kill the timer if one was configured, because now
// we're going to go away when they exit presentation mode
KillTimer( hWnd, 1 );
@@ -7221,19 +7413,19 @@ LRESULT CALLBACK LiveZoomWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM
float newZoomLevel = zoomLevel;
switch( wParam ) {
case 0:
- // zoom in
- if( newZoomLevel < ZOOM_LEVEL_MAX )
+ // zoom in
+ if( newZoomLevel < ZOOM_LEVEL_MAX )
newZoomLevel *= 2;
zoomTelescopeStep = ZOOM_LEVEL_STEP_IN;
break;
case 1:
- if( newZoomLevel > 2 )
+ if( newZoomLevel > 2 )
newZoomLevel /= 2;
else {
- newZoomLevel *= .75;
- if( newZoomLevel < ZOOM_LEVEL_MIN )
+ newZoomLevel *= .75;
+ if( newZoomLevel < ZOOM_LEVEL_MIN )
newZoomLevel = ZOOM_LEVEL_MIN;
}
zoomTelescopeStep = ZOOM_LEVEL_STEP_OUT;
@@ -7260,12 +7452,12 @@ LRESULT CALLBACK LiveZoomWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM
break;
case VK_UP:
- SendMessage( hWnd, WM_MOUSEWHEEL,
+ SendMessage( hWnd, WM_MOUSEWHEEL,
MAKEWPARAM( GetAsyncKeyState( VK_LCONTROL ) != 0 ? MK_CONTROL: 0, WHEEL_DELTA), 0 );
return TRUE;
case VK_DOWN:
- SendMessage( hWnd, WM_MOUSEWHEEL,
+ SendMessage( hWnd, WM_MOUSEWHEEL,
MAKEWPARAM( GetAsyncKeyState( VK_LCONTROL ) != 0 ? MK_CONTROL: 0, -WHEEL_DELTA), 0 );
return TRUE;
}
@@ -7273,10 +7465,10 @@ LRESULT CALLBACK LiveZoomWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM
case WM_DESTROY:
g_hWndLiveZoom = NULL;
break;
-
+
case WM_SIZE:
GetClientRect(hWnd, &rc);
- SetWindowPos(g_hWndLiveZoomMag, NULL,
+ SetWindowPos(g_hWndLiveZoomMag, NULL,
rc.left, rc.top, rc.right, rc.bottom, 0 );
break;
@@ -7352,7 +7544,7 @@ LRESULT CALLBACK LiveZoomWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
- return 0;
+ return 0;
}
@@ -7407,7 +7599,7 @@ HRESULT __stdcall WrapD3D11CreateDevice(
// InitInstance
//
//----------------------------------------------------------------------------
-HWND InitInstance( HINSTANCE hInstance, int nCmdShow )
+HWND InitInstance( HINSTANCE hInstance, int nCmdShow )
{
WNDCLASS wcZoomIt;
HWND hWndMain;
@@ -7423,7 +7615,7 @@ HWND InitInstance( HINSTANCE hInstance, int nCmdShow )
wcZoomIt.cbClsExtra = 0;
wcZoomIt.cbWndExtra = 0;
wcZoomIt.hInstance = hInstance;
- wcZoomIt.hIcon = 0;
+ wcZoomIt.hIcon = 0;
wcZoomIt.hCursor = LoadCursor(NULL, IDC_ARROW);
wcZoomIt.hbrBackground = NULL;
wcZoomIt.lpszMenuName = NULL;
@@ -7435,40 +7627,40 @@ HWND InitInstance( HINSTANCE hInstance, int nCmdShow )
g_LiveZoomToggleKey = 0;
}
- wcZoomIt.style = 0;
- wcZoomIt.lpfnWndProc = (WNDPROC)MainWndProc;
- wcZoomIt.cbClsExtra = 0;
- wcZoomIt.cbWndExtra = 0;
+ wcZoomIt.style = 0;
+ wcZoomIt.lpfnWndProc = (WNDPROC)MainWndProc;
+ wcZoomIt.cbClsExtra = 0;
+ wcZoomIt.cbWndExtra = 0;
wcZoomIt.hInstance = hInstance; wcZoomIt.hIcon = NULL;
wcZoomIt.hCursor = LoadCursor( hInstance, L"NULLCURSOR" );
wcZoomIt.hbrBackground = NULL;
- wcZoomIt.lpszMenuName = NULL;
+ wcZoomIt.lpszMenuName = NULL;
wcZoomIt.lpszClassName = L"ZoomitClass";
if ( ! RegisterClass(&wcZoomIt) )
return FALSE;
- hWndMain = CreateWindowEx( WS_EX_TOOLWINDOW, L"ZoomitClass",
- L"Zoomit Zoom Window",
+ hWndMain = CreateWindowEx( WS_EX_TOOLWINDOW, L"ZoomitClass",
+ L"Zoomit Zoom Window",
WS_POPUP,
- 0, 0,
0, 0,
- NULL,
- NULL,
- hInstance,
+ 0, 0,
+ NULL,
+ NULL,
+ hInstance,
NULL);
- // If window could not be created, return "failure"
+ // If window could not be created, return "failure"
if (!hWndMain )
return NULL;
- // Make the window visible; update its client area; and return "success"
+ // Make the window visible; update its client area; and return "success"
ShowWindow(hWndMain, SW_HIDE);
// Add tray icon
EnableDisableTrayIcon( hWndMain, g_ShowTrayIcon );
- return hWndMain;
+ return hWndMain;
-}
+}
//----------------------------------------------------------------------------
//
@@ -7478,7 +7670,7 @@ HWND InitInstance( HINSTANCE hInstance, int nCmdShow )
int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance,
_In_ PWSTR lpCmdLine, _In_ int nCmdShow )
{
- MSG msg;
+ MSG msg;
HACCEL hAccel;
if( !ShowEula( APPNAME, NULL, NULL )) return 1;
@@ -7541,7 +7733,7 @@ int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance
if( !CreateEvent( NULL, FALSE, FALSE, _T("Local\\ZoomitActive"))) {
CreateEvent( NULL, FALSE, FALSE, _T("ZoomitActive"));
- }
+ }
if( GetLastError() == ERROR_ALREADY_EXISTS ) {
if (g_StartedByPowerToys)
{
@@ -7562,7 +7754,7 @@ int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance
if( local_hWndOptions ) {
SetForegroundWindow( local_hWndOptions );
- SetWindowPos( local_hWndOptions, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOSIZE|SWP_NOMOVE|SWP_SHOWWINDOW );
+ SetWindowPos( local_hWndOptions, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOSIZE|SWP_NOMOVE|SWP_SHOWWINDOW );
break;
}
Sleep( 100 );
diff --git a/src/modules/ZoomIt/ZoomIt/resource.h b/src/modules/ZoomIt/ZoomIt/resource.h
index 568eebda4b..2458e8ce75 100644
--- a/src/modules/ZoomIt/ZoomIt/resource.h
+++ b/src/modules/ZoomIt/ZoomIt/resource.h
@@ -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
diff --git a/src/modules/ZoomIt/ZoomItSettingsInterop/ZoomItSettings.cpp b/src/modules/ZoomIt/ZoomItSettingsInterop/ZoomItSettings.cpp
index 3eb3e235da..25d9678ef2 100644
--- a/src/modules/ZoomIt/ZoomItSettingsInterop/ZoomItSettings.cpp
+++ b/src/modules/ZoomIt/ZoomItSettingsInterop/ZoomItSettings.cpp
@@ -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 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(0)) ? g_RecordScalingGIF : g_RecordScalingMP4;
+ _settings.add_property(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(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(formatValue);
+
+ *static_cast(curSetting->Setting) = formatValue;
+
+ if (oldFormat != newFormat)
+ {
+ formatChanged = true;
+
+ if (oldFormat == static_cast(0))
+ {
+ g_RecordScalingGIF = g_RecordScaling;
+ }
+ else
+ {
+ g_RecordScalingMP4 = g_RecordScaling;
+ }
+
+ if (newFormat == static_cast(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(0))
+ {
+ g_RecordScalingGIF = recordScalingValue.value();
+ }
+ else
+ {
+ g_RecordScalingMP4 = recordScalingValue.value();
+ }
+ }
+
reg.WriteRegSettings(RegSettings);
}
}
diff --git a/src/modules/alwaysontop/AlwaysOnTop/AlwaysOnTop.vcxproj b/src/modules/alwaysontop/AlwaysOnTop/AlwaysOnTop.vcxproj
index a5aac78d10..76016193ed 100644
--- a/src/modules/alwaysontop/AlwaysOnTop/AlwaysOnTop.vcxproj
+++ b/src/modules/alwaysontop/AlwaysOnTop/AlwaysOnTop.vcxproj
@@ -29,6 +29,7 @@
_DEBUG;%(PreprocessorDefinitions)
Disabled
true
+ MultiThreadedDebug
true
@@ -39,6 +40,7 @@
NDEBUG;%(PreprocessorDefinitions)
MaxSpeed
false
+ MultiThreaded
true
true
diff --git a/src/modules/awake/Awake/Core/Native/Bridge.cs b/src/modules/awake/Awake/Core/Native/Bridge.cs
index 44812a4ef7..e82b698a47 100644
--- a/src/modules/awake/Awake/Core/Native/Bridge.cs
+++ b/src/modules/awake/Awake/Core/Native/Bridge.cs
@@ -88,9 +88,6 @@ namespace Awake.Core.Native
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool GetCursorPos(out Point lpPoint);
- [DllImport("user32.dll", SetLastError = true)]
- internal static extern bool ScreenToClient(IntPtr hWnd, ref Point lpPoint);
-
[DllImport("user32.dll", SetLastError = true)]
internal static extern bool GetMessage(out Msg lpMsg, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax);
diff --git a/src/modules/awake/Awake/Core/TrayHelper.cs b/src/modules/awake/Awake/Core/TrayHelper.cs
index 37e2de8e48..16cd5f5795 100644
--- a/src/modules/awake/Awake/Core/TrayHelper.cs
+++ b/src/modules/awake/Awake/Core/TrayHelper.cs
@@ -61,9 +61,8 @@ namespace Awake.Core
Bridge.SetForegroundWindow(hWnd);
- // Get cursor position and convert it to client coordinates
+ // Get cursor position in screen coordinates
Bridge.GetCursorPos(out Models.Point cursorPos);
- Bridge.ScreenToClient(hWnd, ref cursorPos);
// Set menu information
MenuInfo menuInfo = new()
diff --git a/src/modules/cmdpal/CmdPalKeyboardService/CmdPalKeyboardService.vcxproj b/src/modules/cmdpal/CmdPalKeyboardService/CmdPalKeyboardService.vcxproj
index 3a991470bb..eb655fe7e7 100644
--- a/src/modules/cmdpal/CmdPalKeyboardService/CmdPalKeyboardService.vcxproj
+++ b/src/modules/cmdpal/CmdPalKeyboardService/CmdPalKeyboardService.vcxproj
@@ -141,4 +141,43 @@
+
+
+
+
+
+
+ MultiThreadedDebug
+
+
+
+ %(IgnoreSpecificDefaultLibraries);libucrtd.lib
+ %(AdditionalOptions) /defaultlib:ucrtd.lib
+
+
+
+
+
+ MultiThreaded
+
+
+
+ %(IgnoreSpecificDefaultLibraries);libucrt.lib
+ %(AdditionalOptions) /defaultlib:ucrt.lib
+
+
+
+
\ No newline at end of file
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/MainWindow.xaml.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/MainWindow.xaml.cs
index 6ec96dddfe..32f542fc3b 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.UI/MainWindow.xaml.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/MainWindow.xaml.cs
@@ -68,7 +68,6 @@ public sealed partial class MainWindow : WindowEx,
public MainWindow()
{
InitializeComponent();
- HideWindow();
_hwnd = new HWND(WinRT.Interop.WindowNative.GetWindowHandle(this).ToInt32());
diff --git a/src/modules/cmdpal/Microsoft.Terminal.UI/Microsoft.Terminal.UI.vcxproj b/src/modules/cmdpal/Microsoft.Terminal.UI/Microsoft.Terminal.UI.vcxproj
index 31b08eb8f3..e81ff14f6f 100644
--- a/src/modules/cmdpal/Microsoft.Terminal.UI/Microsoft.Terminal.UI.vcxproj
+++ b/src/modules/cmdpal/Microsoft.Terminal.UI/Microsoft.Terminal.UI.vcxproj
@@ -74,11 +74,35 @@
stdcpp20
+
+
+ MultiThreadedDebug
+
+
+
+ %(IgnoreSpecificDefaultLibraries);libucrtd.lib
+ %(AdditionalOptions) /defaultlib:ucrtd.lib /profile /opt:ref /opt:icf
+
stdcpp20
+
+
+ MultiThreaded
+
+
+
+ %(IgnoreSpecificDefaultLibraries);libucrt.lib
+ %(AdditionalOptions) /defaultlib:ucrt.lib /profile /opt:ref /opt:icf
+
diff --git a/src/modules/cmdpal/custom.props b/src/modules/cmdpal/custom.props
index 86541e31cc..2cfa0bbf4b 100644
--- a/src/modules/cmdpal/custom.props
+++ b/src/modules/cmdpal/custom.props
@@ -5,7 +5,7 @@
true
2025
0
- 6
+ 7
Microsoft Command Palette
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/AppListItem.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/AppListItem.cs
index d97bde7037..e99ffae352 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/AppListItem.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/AppListItem.cs
@@ -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;
@@ -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);
diff --git a/src/modules/cmdpal/ext/SamplePagesExtension/Pages/SampleListPageWithDetails.cs b/src/modules/cmdpal/ext/SamplePagesExtension/Pages/SampleListPageWithDetails.cs
index 018c11720c..abf80e3d1e 100644
--- a/src/modules/cmdpal/ext/SamplePagesExtension/Pages/SampleListPageWithDetails.cs
+++ b/src/modules/cmdpal/ext/SamplePagesExtension/Pages/SampleListPageWithDetails.cs
@@ -63,7 +63,7 @@ internal sealed partial class SampleListPageWithDetails : ListPage
Details = new Details()
{
Title = "Hero Image Example",
- HeroImage = new IconInfo("https://m.media-amazon.com/images/M/MV5BNDBkMzVmNGQtYTM2OC00OWRjLTk5OWMtNzNkMDI4NjFjNTZmXkEyXkFqcGdeQXZ3ZXNsZXk@._V1_QL75_UX500_CR0,0,500,281_.jpg"),
+ HeroImage = new IconInfo("https://m.media-amazon.com/images/M/MV5BNDBkMzVmNGQtYTM2OC00OWRjLTk5OWMtNzNkMDI4NjFjNTZmXkEyXkFqcGdeQXZ3ZXNsZXk@._V1_QL75_UX500_CR0,0,500,281_.jpg"), /* #no-spell-check-line */
Body = "It is literally an image of a hero",
},
},
diff --git a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions/Microsoft.CommandPalette.Extensions.vcxproj b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions/Microsoft.CommandPalette.Extensions.vcxproj
index 890d58725e..ba2649f21b 100644
--- a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions/Microsoft.CommandPalette.Extensions.vcxproj
+++ b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions/Microsoft.CommandPalette.Extensions.vcxproj
@@ -68,6 +68,34 @@
true
false
+
+
+
+ MultiThreadedDebug
+
+
+
+ %(IgnoreSpecificDefaultLibraries);libucrtd.lib
+ %(AdditionalOptions) /defaultlib:ucrtd.lib /profile /opt:ref /opt:icf
+
+
+
+
+
+ MultiThreaded
+
+
+
+ %(IgnoreSpecificDefaultLibraries);libucrt.lib
+ %(AdditionalOptions) /defaultlib:ucrt.lib /profile /opt:ref /opt:icf
+
+
diff --git a/src/modules/fancyzones/FancyZones/FancyZones.vcxproj b/src/modules/fancyzones/FancyZones/FancyZones.vcxproj
index 8b249c985e..34e6566bb9 100644
--- a/src/modules/fancyzones/FancyZones/FancyZones.vcxproj
+++ b/src/modules/fancyzones/FancyZones/FancyZones.vcxproj
@@ -26,6 +26,7 @@
_DEBUG;%(PreprocessorDefinitions)
Disabled
true
+ MultiThreadedDebug
true
@@ -36,6 +37,7 @@
NDEBUG;%(PreprocessorDefinitions)
MaxSpeed
false
+ MultiThreaded
true
true
diff --git a/src/modules/keyboardmanager/KeyboardManagerEditor/KeyboardManagerEditor.vcxproj b/src/modules/keyboardmanager/KeyboardManagerEditor/KeyboardManagerEditor.vcxproj
index 01f450714d..271c9f6f08 100644
--- a/src/modules/keyboardmanager/KeyboardManagerEditor/KeyboardManagerEditor.vcxproj
+++ b/src/modules/keyboardmanager/KeyboardManagerEditor/KeyboardManagerEditor.vcxproj
@@ -29,6 +29,7 @@
_DEBUG;%(PreprocessorDefinitions)
Disabled
true
+ MultiThreadedDebug
true
@@ -39,6 +40,7 @@
NDEBUG;%(PreprocessorDefinitions)
MaxSpeed
false
+ MultiThreaded
true
true
diff --git a/src/modules/powerrename/PowerRename.FuzzingTest/PowerRename.FuzzingTest.vcxproj b/src/modules/powerrename/PowerRename.FuzzingTest/PowerRename.FuzzingTest.vcxproj
index aeb8f2da4e..6af52e0243 100644
--- a/src/modules/powerrename/PowerRename.FuzzingTest/PowerRename.FuzzingTest.vcxproj
+++ b/src/modules/powerrename/PowerRename.FuzzingTest/PowerRename.FuzzingTest.vcxproj
@@ -44,6 +44,7 @@
true
NotUsing
/fsanitize=address /fsanitize-coverage=inline-8bit-counters /fsanitize-coverage=edge /fsanitize-coverage=trace-cmp /fsanitize-coverage=trace-div %(AdditionalOptions)
+ MultiThreaded
stdcpplatest
..\;..\lib\;..\..\..\;%(AdditionalIncludeDirectories)
@@ -71,6 +72,7 @@
Console
+ windowscodecs.lib;%(AdditionalDependencies)
diff --git a/src/runner/runner.vcxproj b/src/runner/runner.vcxproj
index 538f51ee5b..239b90d88f 100644
--- a/src/runner/runner.vcxproj
+++ b/src/runner/runner.vcxproj
@@ -28,6 +28,7 @@
v143
None
true
+ true
diff --git a/src/settings-ui/Settings.UI.Library/AIServiceType.cs b/src/settings-ui/Settings.UI.Library/AIServiceType.cs
index 5cbd6a855c..27eccff1cf 100644
--- a/src/settings-ui/Settings.UI.Library/AIServiceType.cs
+++ b/src/settings-ui/Settings.UI.Library/AIServiceType.cs
@@ -17,10 +17,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
FoundryLocal,
Mistral,
Google,
- HuggingFace,
AzureAIInference,
Ollama,
- Anthropic,
- AmazonBedrock,
}
}
diff --git a/src/settings-ui/Settings.UI.Library/AIServiceTypeExtensions.cs b/src/settings-ui/Settings.UI.Library/AIServiceTypeExtensions.cs
index 91f035d23a..5b19212eba 100644
--- a/src/settings-ui/Settings.UI.Library/AIServiceTypeExtensions.cs
+++ b/src/settings-ui/Settings.UI.Library/AIServiceTypeExtensions.cs
@@ -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,
};
}
diff --git a/src/settings-ui/Settings.UI.Library/AIServiceTypeRegistry.cs b/src/settings-ui/Settings.UI.Library/AIServiceTypeRegistry.cs
index fc9e484808..c967f5d840 100644
--- a/src/settings-ui/Settings.UI.Library/AIServiceTypeRegistry.cs
+++ b/src/settings-ui/Settings.UI.Library/AIServiceTypeRegistry.cs
@@ -15,31 +15,6 @@ public static class AIServiceTypeRegistry
{
private static readonly Dictionary 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,
diff --git a/src/settings-ui/Settings.UI.Library/AdvancedPasteMigrationHelper.cs b/src/settings-ui/Settings.UI.Library/AdvancedPasteMigrationHelper.cs
new file mode 100644
index 0000000000..8a606ad83a
--- /dev/null
+++ b/src/settings-ui/Settings.UI.Library/AdvancedPasteMigrationHelper.cs
@@ -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
+{
+ ///
+ /// Helper methods for migrating legacy Advanced Paste settings to the updated schema.
+ ///
+ public static class AdvancedPasteMigrationHelper
+ {
+ ///
+ /// Moves legacy provider configuration snapshots into the strongly-typed providers collection.
+ ///
+ /// The configuration instance to migrate.
+ /// True if the configuration was modified.
+ public static bool MigrateLegacyProviderConfigurations(PasteAIConfiguration configuration)
+ {
+ if (configuration is null)
+ {
+ return false;
+ }
+
+ configuration.Providers ??= new ObservableCollection();
+
+ 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;
+ }
+
+ ///
+ /// Ensures an OpenAI provider exists in the configuration, creating one if necessary.
+ ///
+ /// The configuration instance.
+ /// The ensured provider and a flag indicating whether changes were made.
+ public static (PasteAIProviderDefinition Provider, bool Updated) EnsureOpenAIProvider(PasteAIConfiguration configuration)
+ {
+ return EnsureProvider(configuration, AIServiceType.OpenAI.ToConfigurationString(), null);
+ }
+
+ ///
+ /// Ensures a provider for the supplied service type exists, optionally applying a legacy snapshot.
+ ///
+ /// The configuration instance.
+ /// The persisted service type key.
+ /// An optional snapshot containing legacy values.
+ /// The ensured provider and whether the configuration was updated.
+ public static (PasteAIProviderDefinition Provider, bool Updated) EnsureProvider(PasteAIConfiguration configuration, string serviceTypeKey, AIProviderConfigurationSnapshot snapshot)
+ {
+ if (configuration is null)
+ {
+ return (null, false);
+ }
+
+ configuration.Providers ??= new ObservableCollection();
+
+ 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;
+ }
+ }
+}
diff --git a/src/settings-ui/Settings.UI.Library/AdvancedPasteProperties.cs b/src/settings-ui/Settings.UI.Library/AdvancedPasteProperties.cs
index 200e5e459d..3c377900f8 100644
--- a/src/settings-ui/Settings.UI.Library/AdvancedPasteProperties.cs
+++ b/src/settings-ui/Settings.UI.Library/AdvancedPasteProperties.cs
@@ -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 _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; }
diff --git a/src/settings-ui/Settings.UI.Library/PasteAIProviderDefaults.cs b/src/settings-ui/Settings.UI.Library/PasteAIProviderDefaults.cs
new file mode 100644
index 0000000000..1ccfa753fa
--- /dev/null
+++ b/src/settings-ui/Settings.UI.Library/PasteAIProviderDefaults.cs
@@ -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
+{
+ ///
+ /// Provides default values for Paste AI provider definitions.
+ ///
+ public static class PasteAIProviderDefaults
+ {
+ ///
+ /// Gets the default model name for a given AI service type.
+ ///
+ 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,
+ };
+ }
+ }
+}
diff --git a/src/settings-ui/Settings.UI.Library/ZoomItProperties.cs b/src/settings-ui/Settings.UI.Library/ZoomItProperties.cs
index 9325907a72..1bca1b573a 100644
--- a/src/settings-ui/Settings.UI.Library/ZoomItProperties.cs
+++ b/src/settings-ui/Settings.UI.Library/ZoomItProperties.cs
@@ -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; }
diff --git a/src/settings-ui/Settings.UI/Assets/Settings/Icons/CursorWrap.png b/src/settings-ui/Settings.UI/Assets/Settings/Icons/CursorWrap.png
index 4374dfdc82..c32f1e309a 100644
Binary files a/src/settings-ui/Settings.UI/Assets/Settings/Icons/CursorWrap.png and b/src/settings-ui/Settings.UI/Assets/Settings/Icons/CursorWrap.png differ
diff --git a/src/settings-ui/Settings.UI/Assets/Settings/Icons/FindMyMouse.png b/src/settings-ui/Settings.UI/Assets/Settings/Icons/FindMyMouse.png
index 6cdb55cb66..581c317518 100644
Binary files a/src/settings-ui/Settings.UI/Assets/Settings/Icons/FindMyMouse.png and b/src/settings-ui/Settings.UI/Assets/Settings/Icons/FindMyMouse.png differ
diff --git a/src/settings-ui/Settings.UI/Assets/Settings/Icons/Models/Anthropic.svg b/src/settings-ui/Settings.UI/Assets/Settings/Icons/Models/Anthropic.svg
deleted file mode 100644
index f990d4650f..0000000000
--- a/src/settings-ui/Settings.UI/Assets/Settings/Icons/Models/Anthropic.svg
+++ /dev/null
@@ -1,10 +0,0 @@
-
diff --git a/src/settings-ui/Settings.UI/Assets/Settings/Icons/Models/Bedrock.svg b/src/settings-ui/Settings.UI/Assets/Settings/Icons/Models/Bedrock.svg
deleted file mode 100644
index d7a3d800d9..0000000000
--- a/src/settings-ui/Settings.UI/Assets/Settings/Icons/Models/Bedrock.svg
+++ /dev/null
@@ -1,10 +0,0 @@
-
diff --git a/src/settings-ui/Settings.UI/Assets/Settings/Icons/Models/HuggingFace.svg b/src/settings-ui/Settings.UI/Assets/Settings/Icons/Models/HuggingFace.svg
deleted file mode 100644
index 9fe0aff336..0000000000
--- a/src/settings-ui/Settings.UI/Assets/Settings/Icons/Models/HuggingFace.svg
+++ /dev/null
@@ -1,8 +0,0 @@
-
diff --git a/src/settings-ui/Settings.UI/Assets/Settings/Icons/MouseHighlighter.png b/src/settings-ui/Settings.UI/Assets/Settings/Icons/MouseHighlighter.png
index 7e9a2da1a8..69ed506e99 100644
Binary files a/src/settings-ui/Settings.UI/Assets/Settings/Icons/MouseHighlighter.png and b/src/settings-ui/Settings.UI/Assets/Settings/Icons/MouseHighlighter.png differ
diff --git a/src/settings-ui/Settings.UI/Assets/Settings/Icons/MouseJump.png b/src/settings-ui/Settings.UI/Assets/Settings/Icons/MouseJump.png
index 15568110cc..14d5e71d53 100644
Binary files a/src/settings-ui/Settings.UI/Assets/Settings/Icons/MouseJump.png and b/src/settings-ui/Settings.UI/Assets/Settings/Icons/MouseJump.png differ
diff --git a/src/settings-ui/Settings.UI/Assets/Settings/Icons/MouseWithoutBorders.png b/src/settings-ui/Settings.UI/Assets/Settings/Icons/MouseWithoutBorders.png
index 83d1fbc553..99a8f64ed4 100644
Binary files a/src/settings-ui/Settings.UI/Assets/Settings/Icons/MouseWithoutBorders.png and b/src/settings-ui/Settings.UI/Assets/Settings/Icons/MouseWithoutBorders.png differ
diff --git a/src/settings-ui/Settings.UI/Converters/StringToDouble.cs b/src/settings-ui/Settings.UI/Converters/StringToDouble.cs
new file mode 100644
index 0000000000..fae0618467
--- /dev/null
+++ b/src/settings-ui/Settings.UI/Converters/StringToDouble.cs
@@ -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";
+ }
+ }
+}
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Views/AdvancedPastePage.xaml b/src/settings-ui/Settings.UI/SettingsXAML/Views/AdvancedPastePage.xaml
index 8d6b0afa68..457615bfe5 100644
--- a/src/settings-ui/Settings.UI/SettingsXAML/Views/AdvancedPastePage.xaml
+++ b/src/settings-ui/Settings.UI/SettingsXAML/Views/AdvancedPastePage.xaml
@@ -499,7 +499,7 @@
MinWidth="200"
HorizontalAlignment="Stretch"
Header="Model name"
- PlaceholderText="gpt-4"
+ PlaceholderText="gpt-4o"
Text="{x:Bind ViewModel.PasteAIProviderDraft.ModelName, Mode=TwoWay}" />
"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/",
};
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Views/LightSwitchPage.xaml b/src/settings-ui/Settings.UI/SettingsXAML/Views/LightSwitchPage.xaml
index 7f55e31cd8..29885d564c 100644
--- a/src/settings-ui/Settings.UI/SettingsXAML/Views/LightSwitchPage.xaml
+++ b/src/settings-ui/Settings.UI/SettingsXAML/Views/LightSwitchPage.xaml
@@ -1,24 +1,23 @@

-
-
+
-
+
+
-
+ Click="SyncLocationButton_Click" />
@@ -202,17 +199,22 @@
-
+
-
+
+
-
+
-
+
+
+
+
+
+
+
+
+
-
-
+
-
-
-
+
-
-
-
-
-
-
-
-
-
-
+
+ Text="{x:Bind ViewModel.LocationPanelLightTime, Converter={StaticResource TimeSpanToFriendlyTimeConverter}, Mode=OneWay}"
+ TextAlignment="Left" />
@@ -321,12 +327,12 @@
+ Text="{x:Bind ViewModel.LocationPanelDarkTime, Converter={StaticResource TimeSpanToFriendlyTimeConverter}, Mode=OneWay}"
+ TextAlignment="Left" />
@@ -358,4 +364,4 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Views/LightSwitchPage.xaml.cs b/src/settings-ui/Settings.UI/SettingsXAML/Views/LightSwitchPage.xaml.cs
index 4a8e8905d7..c61b5c5dd8 100644
--- a/src/settings-ui/Settings.UI/SettingsXAML/Views/LightSwitchPage.xaml.cs
+++ b/src/settings-ui/Settings.UI/SettingsXAML/Views/LightSwitchPage.xaml.cs
@@ -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 _sendConfigMsg = ShellPage.SendDefaultIPCMessage;
+ private readonly string appName = "LightSwitch";
+ private readonly SettingsUtils settingsUtils;
+ private readonly Func sendConfigMsg = ShellPage.SendDefaultIPCMessage;
- private readonly ISettingsRepository _generalSettingsRepository;
- private readonly ISettingsRepository _moduleSettingsRepository;
+ private readonly SettingsRepository generalSettingsRepository;
+ private readonly SettingsRepository 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.GetInstance(_settingsUtils);
- _moduleSettingsRepository = SettingsRepository.GetInstance(_settingsUtils);
+ this.generalSettingsRepository = SettingsRepository.GetInstance(this.settingsUtils);
+ this.moduleSettingsRepository = SettingsRepository.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}°, {ViewModel.Longitude}°";
+ 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}°, {ViewModel.Longitude}°";
+ 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}°, {this.ViewModel.Longitude}°";
+
+ 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 csIpcMessage = new(currentSettings);
- SndLightSwitchSettings outSettings = new(ViewModel.ModuleSettings);
+ SndLightSwitchSettings outSettings = new(this.ViewModel.ModuleSettings);
SndModuleSettings 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 generalSettingsRepository, ISettingsRepository moduleSettingsRepository)
+ private void LoadSettings(SettingsRepository generalSettingsRepository, SettingsRepository 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)
- 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;
}
}
}
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Views/ZoomItPage.xaml b/src/settings-ui/Settings.UI/SettingsXAML/Views/ZoomItPage.xaml
index 9b938d14a2..80adbcc590 100644
--- a/src/settings-ui/Settings.UI/SettingsXAML/Views/ZoomItPage.xaml
+++ b/src/settings-ui/Settings.UI/SettingsXAML/Views/ZoomItPage.xaml
@@ -241,6 +241,12 @@
1.0
+
+
+ GIF
+ MP4
+
+
diff --git a/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw b/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw
index 6fff118086..650ac06727 100644
--- a/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw
+++ b/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw
@@ -1,17 +1,17 @@
-
@@ -708,15 +708,6 @@ Please review the placeholder content that represents the final terms and usage
Google Privacy Policy
-
- 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.
-
-
- Anthropic Terms of Service
-
-
- Anthropic Privacy Policy
-
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.
@@ -726,15 +717,6 @@ Please review the placeholder content that represents the final terms and usage
Mistral Privacy Policy
-
- 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.
-
-
- AWS Service Terms
-
-
- AWS Privacy Notice
-
Ollama Terms of Service
@@ -2269,7 +2251,7 @@ Take a moment to preview the various utilities listed or view our comprehensive
Press the **Restart as administrator** button from the File Locksmith UI to also get information on elevated processes that might be using the files.
- Select **View** which is located at the top of File Explorer, followed by **Show**, and then **Preview pane**.
+ 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!
@@ -2695,9 +2677,7 @@ From there, simply click on one of the supported files in the File Explorer and
Use a keyboard shortcut to highlight left and right mouse clicks.
Mouse as in the hardware peripheral.
-
-
-
+
Enable CursorWrap
@@ -2707,8 +2687,6 @@ From there, simply click on one of the supported files in the File Explorer and
Wrap the mouse cursor between monitor edges
-
-
Activation shortcut
@@ -2718,19 +2696,15 @@ From there, simply click on one of the supported files in the File Explorer and
Set shortcut
-
Disable wrapping while dragging
-
-
-
+
Auto-activate on startup
Automatically activate on utility startup
-
-
+
Mouse Pointer Crosshairs
Mouse as in the hardware peripheral.
@@ -5046,6 +5020,9 @@ To record a specific window, enter the hotkey with the Alt key in the opposite m
Scaling
+
+ Format
+
Capture audio input
@@ -5480,16 +5457,13 @@ To record a specific window, enter the hotkey with the Alt key in the opposite m
Select a location
- Select
+ Save
Cancel
-
- Get current location
-
- To calculate the sunrise and sunset, Light Switch needs a location.
+ Detect your location automatically or enter it manually to calculate sunrise and sunset times.
Sunrise
@@ -5497,8 +5471,17 @@ To record a specific window, enter the hotkey with the Alt key in the opposite m
Sunset
-
- Location
+
+ Latitude
+
+
+ Longitude
+
+
+ Detect location
+
+
+ Detect location
Sunrise
@@ -5582,7 +5565,7 @@ To record a specific window, enter the hotkey with the Alt key in the opposite m
Shortcut conflicts
-
+
No conflicts found
@@ -5693,4 +5676,7 @@ To record a specific window, enter the hotkey with the Alt key in the opposite m
Backup count
+
+ Set Location
+
\ No newline at end of file
diff --git a/src/settings-ui/Settings.UI/ViewModels/AdvancedPasteViewModel.cs b/src/settings-ui/Settings.UI/ViewModels/AdvancedPasteViewModel.cs
index 0d90899866..92a85d96c2 100644
--- a/src/settings-ui/Settings.UI/ViewModels/AdvancedPasteViewModel.cs
+++ b/src/settings-ui/Settings.UI/ViewModels/AdvancedPasteViewModel.cs
@@ -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 SendConfigMSG { get; }
+ private static readonly HashSet 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 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() == 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();
+
+ bool configurationUpdated = AdvancedPasteMigrationHelper.MigrateLegacyProviderConfigurations(pasteConfig);
+
SubscribeToPasteAIProviders(pasteConfig);
+
+ if (configurationUpdated)
+ {
+ SaveAndNotifySettings();
+ OnPropertyChanged(nameof(PasteAIConfiguration));
+ }
}
private static string RetrieveCredentialValue(string credentialResource, string credentialUserName)
diff --git a/src/settings-ui/Settings.UI/ViewModels/LightSwitchViewModel.cs b/src/settings-ui/Settings.UI/ViewModels/LightSwitchViewModel.cs
index 3f9ea48c18..911ec81aa2 100644
--- a/src/settings-ui/Settings.UI/ViewModels/LightSwitchViewModel.cs
+++ b/src/settings-ui/Settings.UI/ViewModels/LightSwitchViewModel.cs
@@ -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;
diff --git a/src/settings-ui/Settings.UI/ViewModels/MouseUtilsViewModel.cs b/src/settings-ui/Settings.UI/ViewModels/MouseUtilsViewModel.cs
index 518b2a6fa4..27695d1037 100644
--- a/src/settings-ui/Settings.UI/ViewModels/MouseUtilsViewModel.cs
+++ b/src/settings-ui/Settings.UI/ViewModels/MouseUtilsViewModel.cs
@@ -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());
diff --git a/src/settings-ui/Settings.UI/ViewModels/ZoomItViewModel.cs b/src/settings-ui/Settings.UI/ViewModels/ZoomItViewModel.cs
index 9e99c0e22f..2d93deef81 100644
--- a/src/settings-ui/Settings.UI/ViewModels/ZoomItViewModel.cs
+++ b/src/settings-ui/Settings.UI/ViewModels/ZoomItViewModel.cs
@@ -652,6 +652,54 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
}
}
+ public int RecordFormatIndex
+ {
+ get
+ {
+ if (_zoomItSettings.Properties.RecordFormat.Value == "GIF")
+ {
+ return 0;
+ }
+
+ if (_zoomItSettings.Properties.RecordFormat.Value == "MP4")
+ {
+ return 1;
+ }
+
+ return 0;
+ }
+
+ set
+ {
+ int format = 0;
+ if (_zoomItSettings.Properties.RecordFormat.Value == "GIF")
+ {
+ format = 0;
+ }
+
+ if (_zoomItSettings.Properties.RecordFormat.Value == "MP4")
+ {
+ format = 1;
+ }
+
+ if (format != value)
+ {
+ _zoomItSettings.Properties.RecordFormat.Value = value == 0 ? "GIF" : "MP4";
+ OnPropertyChanged(nameof(RecordFormatIndex));
+ NotifySettingsChanged();
+
+ // Reload settings to get the new format's scaling value
+ var reloadedSettings = global::PowerToys.ZoomItSettingsInterop.ZoomItSettings.LoadSettingsJson();
+ var reloaded = JsonSerializer.Deserialize(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;
diff --git a/tools/BugReportTool/BugReportTool/ReportGPOValues.cpp b/tools/BugReportTool/BugReportTool/ReportGPOValues.cpp
index 9ef9505fa5..edd48839d2 100644
--- a/tools/BugReportTool/BugReportTool/ReportGPOValues.cpp
+++ b/tools/BugReportTool/BugReportTool/ReportGPOValues.cpp
@@ -92,7 +92,6 @@ void ReportGPOValues(const std::filesystem::path &tmpDir)
report << "getAllowedAdvancedPasteAzureAIInferenceValue: " << gpo_rule_configured_to_string(powertoys_gpo::getAllowedAdvancedPasteAzureAIInferenceValue()) << std::endl;
report << "getAllowedAdvancedPasteMistralValue: " << gpo_rule_configured_to_string(powertoys_gpo::getAllowedAdvancedPasteMistralValue()) << std::endl;
report << "getAllowedAdvancedPasteGoogleValue: " << gpo_rule_configured_to_string(powertoys_gpo::getAllowedAdvancedPasteGoogleValue()) << std::endl;
- report << "getAllowedAdvancedPasteAnthropicValue: " << gpo_rule_configured_to_string(powertoys_gpo::getAllowedAdvancedPasteAnthropicValue()) << std::endl;
report << "getAllowedAdvancedPasteOllamaValue: " << gpo_rule_configured_to_string(powertoys_gpo::getAllowedAdvancedPasteOllamaValue()) << std::endl;
report << "getAllowedAdvancedPasteFoundryLocalValue: " << gpo_rule_configured_to_string(powertoys_gpo::getAllowedAdvancedPasteFoundryLocalValue()) << std::endl;
report << "getConfiguredMwbClipboardSharingEnabledValue: " << gpo_rule_configured_to_string(powertoys_gpo::getConfiguredMwbClipboardSharingEnabledValue()) << std::endl;
diff --git a/tools/project_template/ModuleTemplate/ModuleTemplate.vcxproj b/tools/project_template/ModuleTemplate/ModuleTemplate.vcxproj
index 39c656a6cc..028007de67 100644
--- a/tools/project_template/ModuleTemplate/ModuleTemplate.vcxproj
+++ b/tools/project_template/ModuleTemplate/ModuleTemplate.vcxproj
@@ -46,6 +46,7 @@
true
_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)
true
+ MultiThreadedDebug
stdcpplatest
@@ -63,6 +64,7 @@
true
NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)
true
+ MultiThreaded
stdcpplatest
diff --git a/tools/project_template/ModuleTemplate/ModuleTemplateCompileTest.vcxproj b/tools/project_template/ModuleTemplate/ModuleTemplateCompileTest.vcxproj
index 7890d314f8..0aac292bac 100644
--- a/tools/project_template/ModuleTemplate/ModuleTemplateCompileTest.vcxproj
+++ b/tools/project_template/ModuleTemplate/ModuleTemplateCompileTest.vcxproj
@@ -47,6 +47,7 @@
Disabled
true
_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)
+ MultiThreadedDebug
stdcpplatest
@@ -63,6 +64,7 @@
true
true
NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)
+ MultiThreaded
stdcpplatest