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 @@

Installation - . + · Documentation - . + · Blog - . + · Release notes



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 @@  - - + - + + - - - + - - - + - - - - - - - - - - + + 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