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 4ed72eaa7a..d113aa3519 100644
--- a/.github/actions/spell-check/expect.txt
+++ b/.github/actions/spell-check/expect.txt
@@ -65,6 +65,7 @@ APIIs
Apm
APPBARDATA
APPEXECLINK
+appext
APPLICATIONFRAMEHOST
appmanifest
APPMODEL
@@ -100,7 +101,6 @@ ATX
ATRIOX
aumid
authenticode
-Authenticode
AUTOBUDDY
AUTOCHECKBOX
AUTOHIDE
@@ -187,6 +187,7 @@ CAPTUREBLT
CAPTURECHANGED
CARETBLINKING
CAtl
+CBN
cch
CCHDEVICENAME
CCHFORMNAME
@@ -315,7 +316,6 @@ CURSORINFO
cursorpos
CURSORSHOWING
CURSORWRAP
-CursorWrap
customaction
CUSTOMACTIONTEST
CUSTOMFORMATPLACEHOLDER
@@ -415,6 +415,9 @@ DNLEN
DONOTROUND
DONTVALIDATEPATH
dotnet
+downsampled
+downsampling
+Downsampled
downscale
DPICHANGED
DPIs
@@ -431,7 +434,6 @@ DSTINVERT
DString
DSVG
dto
-DTo
DUMMYUNIONNAME
dutil
DVASPECT
@@ -465,7 +467,6 @@ EDITKEYBOARD
EDITSHORTCUTS
EDITTEXT
EFile
-ekus
eku
emojis
ENABLEDELAYEDEXPANSION
@@ -601,6 +602,7 @@ getfilesiginforedist
geolocator
GETHOTKEY
GETICON
+GETLBTEXT
GETMINMAXINFO
GETNONCLIENTMETRICS
GETPROPERTYSTOREFLAGS
@@ -608,6 +610,7 @@ GETSCREENSAVERRUNNING
GETSECKEY
GETSTICKYKEYS
GETTEXTLENGTH
+GIFs
gitmodules
GHND
GMEM
@@ -618,6 +621,7 @@ GPOCA
gpp
gpu
gradians
+grctlext
Gridcustomlayout
GSM
gtm
@@ -1150,7 +1154,6 @@ NONCLIENTMETRICSW
NONELEVATED
nonspace
nonstd
-nullrefs
NOOWNERZORDER
NOPARENTNOTIFY
NOPREFIX
@@ -1190,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 34e4ce4340..62cb993d5d 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/README.md b/README.md
index e737281523..067d6d6f50 100644
--- a/README.md
+++ b/README.md
@@ -10,11 +10,11 @@
diff --git a/doc/images/icons/CursorWrap.png b/doc/images/icons/CursorWrap.png
new file mode 100644
index 0000000000..20db84fc9a
Binary files /dev/null and b/doc/images/icons/CursorWrap.png differ
diff --git a/doc/images/icons/Find My Mouse.png b/doc/images/icons/Find My Mouse.png
index 71dd994569..82fbe59800 100644
Binary files a/doc/images/icons/Find My Mouse.png and b/doc/images/icons/Find My Mouse.png differ
diff --git a/doc/images/icons/Mouse Highlighter.png b/doc/images/icons/Mouse Highlighter.png
index b06843d941..0feb5cc15a 100644
Binary files a/doc/images/icons/Mouse Highlighter.png and b/doc/images/icons/Mouse Highlighter.png differ
diff --git a/doc/images/icons/MouseJump.png b/doc/images/icons/MouseJump.png
new file mode 100644
index 0000000000..2fbe450ac2
Binary files /dev/null and b/doc/images/icons/MouseJump.png differ
diff --git a/doc/images/icons/MouseWithoutBorders.png b/doc/images/icons/MouseWithoutBorders.png
index a29adf7d11..ee66893cbd 100644
Binary files a/doc/images/icons/MouseWithoutBorders.png and b/doc/images/icons/MouseWithoutBorders.png differ
diff --git a/src/common/UITestAutomation/SettingsConfigHelper.cs b/src/common/UITestAutomation/SettingsConfigHelper.cs
new file mode 100644
index 0000000000..833ec4f19d
--- /dev/null
+++ b/src/common/UITestAutomation/SettingsConfigHelper.cs
@@ -0,0 +1,175 @@
+// 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.Generic;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.IO;
+using System.Linq;
+using System.Text.Json;
+using Microsoft.PowerToys.Settings.UI.Library;
+using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
+using Microsoft.PowerToys.Settings.UI.Library.Utilities;
+
+namespace Microsoft.PowerToys.UITest
+{
+ ///
+ /// Helper class for configuring PowerToys settings for UI tests.
+ ///
+ public class SettingsConfigHelper
+ {
+ private static readonly JsonSerializerOptions IndentedJsonOptions = new() { WriteIndented = true };
+ private static readonly SettingsUtils SettingsUtils = new SettingsUtils();
+
+ ///
+ /// Configures global PowerToys settings to enable only specified modules and disable all others.
+ ///
+ /// Array of module names to enable (e.g., "Peek", "FancyZones"). All other modules will be disabled.
+ /// Thrown when modulesToEnable is null.
+ /// Thrown when settings file operations fail.
+ [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "This is test code and will not be trimmed")]
+ [UnconditionalSuppressMessage("AOT", "IL3050", Justification = "This is test code and will not be AOT compiled")]
+ public static void ConfigureGlobalModuleSettings(params string[] modulesToEnable)
+ {
+ ArgumentNullException.ThrowIfNull(modulesToEnable);
+
+ try
+ {
+ GeneralSettings settings;
+ try
+ {
+ settings = SettingsUtils.GetSettingsOrDefault();
+ }
+ catch (Exception ex)
+ {
+ Debug.WriteLine($"Failed to load settings, creating defaults: {ex.Message}");
+ settings = new GeneralSettings();
+ }
+
+ string settingsJson = settings.ToJsonString();
+ using (JsonDocument doc = JsonDocument.Parse(settingsJson))
+ {
+ var options = new JsonSerializerOptions { WriteIndented = true };
+ var root = doc.RootElement.Clone();
+
+ if (root.TryGetProperty("enabled", out var enabledElement))
+ {
+ var enabledModules = new Dictionary();
+
+ foreach (var property in enabledElement.EnumerateObject())
+ {
+ string moduleName = property.Name;
+
+ bool shouldEnable = Array.Exists(modulesToEnable, m => string.Equals(m, moduleName, StringComparison.Ordinal));
+ enabledModules[moduleName] = shouldEnable;
+ }
+
+ var settingsDict = JsonSerializer.Deserialize>(settingsJson);
+ if (settingsDict != null)
+ {
+ settingsDict["enabled"] = enabledModules;
+ settingsJson = JsonSerializer.Serialize(settingsDict, IndentedJsonOptions);
+ }
+ }
+ }
+
+ SettingsUtils.SaveSettings(settingsJson);
+
+ string enabledList = modulesToEnable.Length > 0 ? string.Join(", ", modulesToEnable) : "none";
+ Debug.WriteLine($"Successfully updated global settings");
+ Debug.WriteLine($"Enabled modules: {enabledList}");
+ }
+ catch (Exception ex)
+ {
+ Debug.WriteLine($"ERROR in ConfigureGlobalModuleSettings: {ex.Message}");
+ throw new InvalidOperationException($"Failed to configure global module settings: {ex.Message}", ex);
+ }
+ }
+
+ ///
+ /// Updates a module's settings file. If the file doesn't exist, creates it with default content.
+ /// If the file exists, reads it and applies the provided update function to modify the settings.
+ ///
+ /// The name of the module (e.g., "Peek", "FancyZones").
+ /// The default JSON content to use if the settings file doesn't exist.
+ ///
+ /// A callback function that modifies the settings dictionary. The function receives the deserialized settings
+ /// and should modify it in-place. The function should accept a Dictionary<string, object> and not return a value.
+ /// Example: (settings) => { ((Dictionary<string, object>)settings["properties"])["SomeSetting"] = newValue; }
+ ///
+ /// Thrown when moduleName or updateSettingsAction is null.
+ /// Thrown when settings file operations fail.
+ [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "This is test code and will not be trimmed")]
+ [UnconditionalSuppressMessage("AOT", "IL3050", Justification = "This is test code and will not be AOT compiled")]
+ public static void UpdateModuleSettings(
+ string moduleName,
+ string defaultSettingsContent,
+ Action> updateSettingsAction)
+ {
+ ArgumentNullException.ThrowIfNull(moduleName);
+ ArgumentNullException.ThrowIfNull(updateSettingsAction);
+
+ try
+ {
+ // Build the path to the module settings file
+ string powerToysSettingsDirectory = Path.Combine(
+ Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
+ "Microsoft",
+ "PowerToys");
+
+ string moduleDirectory = Path.Combine(powerToysSettingsDirectory, moduleName);
+ string settingsPath = Path.Combine(moduleDirectory, "settings.json");
+
+ // Ensure directory exists
+ Directory.CreateDirectory(moduleDirectory);
+
+ // Read existing settings or use default
+ string existingJson = string.Empty;
+ if (File.Exists(settingsPath))
+ {
+ existingJson = File.ReadAllText(settingsPath);
+ }
+
+ Dictionary? settings;
+
+ // If file doesn't exist or is empty, create from defaults
+ if (string.IsNullOrWhiteSpace(existingJson))
+ {
+ if (string.IsNullOrWhiteSpace(defaultSettingsContent))
+ {
+ throw new ArgumentException("Default settings content must be provided when file doesn't exist.", nameof(defaultSettingsContent));
+ }
+
+ settings = JsonSerializer.Deserialize>(defaultSettingsContent)
+ ?? throw new InvalidOperationException($"Failed to deserialize default settings for {moduleName}");
+
+ Debug.WriteLine($"Created default settings for {moduleName} at {settingsPath}");
+ }
+ else
+ {
+ // Parse existing settings
+ settings = JsonSerializer.Deserialize>(existingJson)
+ ?? throw new InvalidOperationException($"Failed to deserialize existing settings for {moduleName}");
+
+ Debug.WriteLine($"Loaded existing settings for {moduleName} from {settingsPath}");
+ }
+
+ // Apply the update action to modify settings
+ updateSettingsAction(settings);
+
+ // Serialize and save the updated settings using SettingsUtils
+ string updatedJson = JsonSerializer.Serialize(settings, IndentedJsonOptions);
+ SettingsUtils.SaveSettings(updatedJson, moduleName);
+
+ Debug.WriteLine($"Successfully updated settings for {moduleName}");
+ }
+ catch (Exception ex)
+ {
+ Debug.WriteLine($"ERROR in UpdateModuleSettings for {moduleName}: {ex.Message}");
+ throw new InvalidOperationException($"Failed to update settings for {moduleName}: {ex.Message}", ex);
+ }
+ }
+ }
+}
diff --git a/src/common/UITestAutomation/UITestAutomation.csproj b/src/common/UITestAutomation/UITestAutomation.csproj
index add7acfeb9..549b8a430b 100644
--- a/src/common/UITestAutomation/UITestAutomation.csproj
+++ b/src/common/UITestAutomation/UITestAutomation.csproj
@@ -8,7 +8,7 @@
enable
true
true
- net9.0-windows10.0.22621.0
+ net9.0-windows10.0.26100.0
true
false
@@ -21,4 +21,8 @@
+
+
+
+
diff --git a/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/Controls/PromptBox.xaml b/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/Controls/PromptBox.xaml
index 9a6b8d04c9..8b7e3e5c6a 100644
--- a/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/Controls/PromptBox.xaml
+++ b/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/Controls/PromptBox.xaml
@@ -542,7 +542,10 @@
Source="{x:Bind ViewModel.ActiveAIProvider?.ServiceType, Mode=OneWay, Converter={StaticResource ServiceTypeToIconConverter}}" />
-
+
+ NavigateUri="{x:Bind ViewModel.TermsLinkUri, Mode=OneWay}"
+ Visibility="{x:Bind ViewModel.HasTermsLink, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}" />
+ NavigateUri="{x:Bind ViewModel.PrivacyLinkUri, Mode=OneWay}"
+ Visibility="{x:Bind ViewModel.HasPrivacyLink, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}" />
diff --git a/src/modules/AdvancedPaste/AdvancedPaste/ViewModels/OptionsViewModel.cs b/src/modules/AdvancedPaste/AdvancedPaste/ViewModels/OptionsViewModel.cs
index f183efee53..467497b9a9 100644
--- a/src/modules/AdvancedPaste/AdvancedPaste/ViewModels/OptionsViewModel.cs
+++ b/src/modules/AdvancedPaste/AdvancedPaste/ViewModels/OptionsViewModel.cs
@@ -71,6 +71,11 @@ namespace AdvancedPaste.ViewModels
[NotifyPropertyChangedFor(nameof(IsCustomAIAvailable))]
[NotifyPropertyChangedFor(nameof(AllowedAIProviders))]
[NotifyPropertyChangedFor(nameof(ActiveAIProvider))]
+ [NotifyPropertyChangedFor(nameof(ActiveAIProviderTooltip))]
+ [NotifyPropertyChangedFor(nameof(TermsLinkUri))]
+ [NotifyPropertyChangedFor(nameof(PrivacyLinkUri))]
+ [NotifyPropertyChangedFor(nameof(HasTermsLink))]
+ [NotifyPropertyChangedFor(nameof(HasPrivacyLink))]
private bool _isAllowedByGPO;
[ObservableProperty]
@@ -187,6 +192,35 @@ namespace AdvancedPaste.ViewModels
}
}
+ private AIServiceTypeMetadata GetActiveProviderMetadata()
+ {
+ var provider = ActiveAIProvider ?? AllowedAIProviders.FirstOrDefault();
+ var serviceType = provider?.ServiceTypeKind ?? AIServiceType.OpenAI;
+ return AIServiceTypeRegistry.GetMetadata(serviceType);
+ }
+
+ public Uri TermsLinkUri
+ {
+ get
+ {
+ var metadata = GetActiveProviderMetadata();
+ return metadata.HasTermsLink ? metadata.TermsUri : null;
+ }
+ }
+
+ public Uri PrivacyLinkUri
+ {
+ get
+ {
+ var metadata = GetActiveProviderMetadata();
+ return metadata.HasPrivacyLink ? metadata.PrivacyUri : null;
+ }
+ }
+
+ public bool HasTermsLink => GetActiveProviderMetadata().HasTermsLink;
+
+ public bool HasPrivacyLink => GetActiveProviderMetadata().HasPrivacyLink;
+
public bool ClipboardHasData => AvailableClipboardFormats != ClipboardFormat.None;
public bool ClipboardHasDataForCustomAI => PasteFormat.SupportsClipboardFormats(CustomAIFormat, AvailableClipboardFormats);
@@ -276,8 +310,8 @@ namespace AdvancedPaste.ViewModels
OnPropertyChanged(nameof(IsAdvancedAIEnabled));
OnPropertyChanged(nameof(AIProviders));
OnPropertyChanged(nameof(AllowedAIProviders));
- OnPropertyChanged(nameof(ActiveAIProvider));
- OnPropertyChanged(nameof(ActiveAIProviderTooltip));
+
+ NotifyActiveProviderChanged();
EnqueueRefreshPasteFormats();
}
@@ -316,8 +350,17 @@ namespace AdvancedPaste.ViewModels
}
}
+ NotifyActiveProviderChanged();
+ }
+
+ private void NotifyActiveProviderChanged()
+ {
OnPropertyChanged(nameof(ActiveAIProvider));
OnPropertyChanged(nameof(ActiveAIProviderTooltip));
+ OnPropertyChanged(nameof(TermsLinkUri));
+ OnPropertyChanged(nameof(PrivacyLinkUri));
+ OnPropertyChanged(nameof(HasTermsLink));
+ OnPropertyChanged(nameof(HasPrivacyLink));
}
private void RefreshPasteFormats()
@@ -836,6 +879,7 @@ namespace AdvancedPaste.ViewModels
UpdateAIProviderActiveFlags();
OnPropertyChanged(nameof(AIProviders));
+ NotifyActiveProviderChanged();
EnqueueRefreshPasteFormats();
}
diff --git a/src/modules/NewPlus/NewShellExtensionContextMenu.win10/NewPlus.ShellExtension.win10.vcxproj b/src/modules/NewPlus/NewShellExtensionContextMenu.win10/NewPlus.ShellExtension.win10.vcxproj
index 74d87c5623..fbe27a7af7 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)Generated Files
false
stdcpplatest
@@ -67,7 +67,7 @@
NDEBUG;NEWPLUSSHELLEXTENSIONWIN10_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)
true
Use
- ..\..\..\common\Telemetry;..\..\;..\..\..\;%(AdditionalIncludeDirectories);..\NewShellExtensionContextMenu
+ ..\..\..\common\Telemetry;..\..\;..\..\..\;%(AdditionalIncludeDirectories);..\NewShellExtensionContextMenu;$(MSBuildThisFileDirectory)Generated Files
false
stdcpplatest
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..5bad5e7670 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,8 +121,8 @@ 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 "Copyright 2006-2025 Mark Russinovich",IDC_COPYRIGHT,42,17,231,8
+ 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
ICON "APPICON",IDC_STATIC,12,9,20,20
@@ -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 f12898dbd4..0907ff3554 100644
--- a/src/modules/ZoomIt/ZoomIt/ZoomIt.vcxproj
+++ b/src/modules/ZoomIt/ZoomIt/ZoomIt.vcxproj
@@ -234,6 +234,7 @@
NotUsing
NotUsing
+
Use
@@ -288,6 +289,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 fadc760339..12faf50fd4 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
//
//----------------------------------------------------------------------------
@@ -3127,7 +3233,7 @@ std::future> CaptureScreenshotAsync(winrt::IDire
winrt::com_ptr d3dContext;
d3dDevice->GetImmediateContext(d3dContext.put());
- // Creating our frame pool with CreateFreeThreaded means that we
+ // 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.
@@ -3165,7 +3271,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;
@@ -3784,15 +3936,22 @@ LRESULT APIENTRY MainWndProc(
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 +3981,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 +4003,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 +4012,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 +4021,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 +4144,7 @@ LRESULT APIENTRY MainWndProc(
// Highlight is not supported in LiveDraw
g_PenColor |= 0xFF << 24;
}
- }
+ }
break;
case SNIP_SAVE_HOTKEY:
@@ -4156,7 +4315,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 +4338,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 +4365,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 +4382,7 @@ LRESULT APIENTRY MainWndProc(
if( g_RecordCropping == TRUE )
{
break;
- }
+ }
// Start screen recording
try
@@ -4243,7 +4404,7 @@ LRESULT APIENTRY MainWndProc(
{
// Already recording
break;
- }
+ }
g_RecordCropping = TRUE;
@@ -4334,8 +4495,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 +4514,7 @@ LRESULT APIENTRY MainWndProc(
if( g_RecordToggle == FALSE )
{
g_RecordToggle = TRUE;
+
#ifdef __ZOOMIT_POWERTOYS__
if( g_StartedByPowerToys )
{
@@ -4379,7 +4541,7 @@ LRESULT APIENTRY MainWndProc(
break;
}
-
+
OutputDebug( L"ZOOM HOTKEY: %d\n", lParam);
if( g_TimerActive ) {
@@ -4444,9 +4606,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 +4623,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 +4704,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 +4714,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 +4732,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 +4775,8 @@ 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;
else
zoomLevel = zoomTelescopeTarget;
SetTimer( hWnd, 1, ZOOM_LEVEL_STEP_TIME, NULL );
@@ -4629,7 +4791,7 @@ 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;
SetTimer( hWnd, 2, ZOOM_LEVEL_STEP_TIME, NULL );
@@ -4682,7 +4844,7 @@ LRESULT APIENTRY MainWndProc(
}
break;
- case WM_POINTERDOWN:
+ case WM_POINTERDOWN:
OutputDebug(L"WM_POINTERDOWN\n");
penInverted = IsPenInverted(wParam);
if (!penInverted) {
@@ -4719,15 +4881,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 +4908,7 @@ LRESULT APIENTRY MainWndProc(
while( delta-- ) {
if( zoomIn ) {
-
+
if( zoomTelescopeTarget < ZOOM_LEVEL_MAX ) {
if( zoomTelescopeTarget < 2 ) {
@@ -4754,17 +4916,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 +4937,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 +4971,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 +4989,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 +5096,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 +5126,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 +5147,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 +5239,10 @@ LRESULT APIENTRY MainWndProc(
}
DrawTypingCursor( hWnd, &textPt, hdcScreenCompat, hdcScreenCursorCompat, &cursorRc );
}
- }
+ }
break;
}
- switch (wParam) {
+ switch (wParam) {
case 'R':
case 'B':
case 'Y':
@@ -5089,7 +5251,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 +5303,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 +5322,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 +5342,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 +5377,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 +5405,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 +5435,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;
@@ -5318,7 +5480,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 +5492,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 +5527,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 +5541,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 +5608,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 +5628,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 +5664,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 +5721,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 +5770,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 +5788,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 +5820,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 +5828,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 +5847,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 +5941,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 +5962,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 +6005,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 +6074,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 +6131,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 +6309,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 +6416,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 +6451,7 @@ LRESULT APIENTRY MainWndProc(
0,
copyWidth, copyHeight,
SRCCOPY | CAPTUREBLT );
-
+
SavePng( targetFilePath, hSaveBitmap );
}
}
@@ -6362,10 +6531,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 +6544,7 @@ LRESULT APIENTRY MainWndProc(
}
break;
- case IDC_DRAW:
+ case IDC_DRAW:
PostMessage( hWnd, WM_HOTKEY, DRAW_HOTKEY, 1 );
break;
@@ -6459,10 +6628,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 +6673,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 +6696,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 +6704,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,7 +6730,7 @@ LRESULT APIENTRY MainWndProc(
if( breakTimeout == 0 && g_BreakPlaySoundFile ) {
PlaySound( g_BreakSoundFile, NULL, SND_FILENAME|SND_ASYNC );
- }
+ }
break;
case 2:
@@ -6579,9 +6748,9 @@ LRESULT APIENTRY MainWndProc(
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,
+ SetCursorPos( monInfo.rcMonitor.left + cursorPos.x,
monInfo.rcMonitor.top + cursorPos.y );
- }
+ }
} else {
@@ -6607,13 +6776,13 @@ LRESULT APIENTRY MainWndProc(
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);
+ 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.right - rcTargetWindow.left,
rcTargetWindow.bottom - rcTargetWindow.top, 0 );
hTargetWindow = NULL;
}
@@ -6670,7 +6839,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 +6849,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 +6880,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 +6906,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 +6915,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 +6930,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 +6982,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 +6990,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 +7075,7 @@ LRESULT CALLBACK LiveZoomWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM
if( !startedInPresentationMode ) {
SetTimer( hWnd, 1, LIVEZOOM_WINDOW_TIMEOUT, NULL );
- }
+ }
}
break;
@@ -6934,7 +7103,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 +7143,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 +7181,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 +7197,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 +7209,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 +7228,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 +7247,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 +7259,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 +7271,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 +7363,7 @@ LRESULT CALLBACK LiveZoomWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM
DestroyWindow( hWnd );
}
- }
+ }
}
break;
}
@@ -7207,9 +7376,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 +7390,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 +7429,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 +7442,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 +7521,7 @@ LRESULT CALLBACK LiveZoomWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
- return 0;
+ return 0;
}
@@ -7407,7 +7576,7 @@ HRESULT __stdcall WrapD3D11CreateDevice(
// InitInstance
//
//----------------------------------------------------------------------------
-HWND InitInstance( HINSTANCE hInstance, int nCmdShow )
+HWND InitInstance( HINSTANCE hInstance, int nCmdShow )
{
WNDCLASS wcZoomIt;
HWND hWndMain;
@@ -7423,7 +7592,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 +7604,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 +7647,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 +7710,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 +7731,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/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/Microsoft.CmdPal.UI/Helpers/GlobalErrorHandler.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/Helpers/GlobalErrorHandler.cs
index 0b55b03615..9b5ac21364 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Helpers/GlobalErrorHandler.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Helpers/GlobalErrorHandler.cs
@@ -51,10 +51,10 @@ internal sealed partial class GlobalErrorHandler
// without its exception being observed. It is NOT raised immediately
// when the Task faults; timing depends on GC finalization.
e.SetObserved();
- HandleException(e.Exception, Context.UnobservedTaskException, isRecoverable: true);
+ HandleException(e.Exception, Context.UnobservedTaskException);
}
- private void HandleException(Exception ex, Context context, bool isRecoverable = false)
+ private static void HandleException(Exception ex, Context context)
{
Logger.LogError($"Unhandled exception detected ({context})", ex);
@@ -70,10 +70,25 @@ internal sealed partial class GlobalErrorHandler
StoreReport(report, storeOnDesktop: false);
+ string message;
+ string caption;
+ try
+ {
+ message = ResourceLoaderInstance.GetString("GlobalErrorHandler_CrashMessageBox_Message");
+ caption = ResourceLoaderInstance.GetString("GlobalErrorHandler_CrashMessageBox_Caption");
+ }
+ catch
+ {
+ // The resource loader may not be available if the exception occurred during startup.
+ // Fall back to hardcoded strings in that case.
+ message = "Command Palette has encountered a fatal error and must close.";
+ caption = "Command Palette - Fatal error";
+ }
+
PInvoke.MessageBox(
HWND.Null,
- "Command Palette has encountered a fatal error and must close.\n\nAn error report has been saved to your desktop.",
- "Unhandled Error",
+ message,
+ caption,
MESSAGEBOX_STYLE.MB_ICONERROR);
}
}
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.CmdPal.UI/Strings/en-us/Resources.resw b/src/modules/cmdpal/Microsoft.CmdPal.UI/Strings/en-us/Resources.resw
index 13cda5925b..8d15c39b39 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Strings/en-us/Resources.resw
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Strings/en-us/Resources.resw
@@ -431,8 +431,8 @@ Right-click to remove the key combination, thereby deactivating the shortcut.
Last Position
Reopen the window where it was last closed
-
-
+
+
Settings
@@ -493,28 +493,34 @@ Right-click to remove the key combination, thereby deactivating the shortcut.
Reloading extensions..
-
+
Discover more extensions
-
+
Find more extensions on the Microsoft Store or WinGet.
-
+
Learn how to create your own extensions
-
+
Find extensions on the Microsoft Store
-
+
Microsoft Store
-
+
Find extensions on WinGet
-
+
Microsoft Store
-
+
Search extensions
+
+ Command Palette has encountered a fatal error and must close.
+
+
+ Command Palette - Fatal error
+
\ No newline at end of file
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/imageresizer/tests/Models/ResizeBatchTests.cs b/src/modules/imageresizer/tests/Models/ResizeBatchTests.cs
index 36a17ceb19..f45fc28e6a 100644
--- a/src/modules/imageresizer/tests/Models/ResizeBatchTests.cs
+++ b/src/modules/imageresizer/tests/Models/ResizeBatchTests.cs
@@ -10,7 +10,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
-
+using ImageResizer.Properties;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using Moq.Protected;
@@ -101,7 +101,9 @@ namespace ImageResizer.Models
private static ResizeBatch CreateBatch(Action executeAction)
{
var mock = new Mock { CallBase = true };
- mock.Protected().Setup("Execute", ItExpr.IsAny()).Callback(executeAction);
+ mock.Protected()
+ .Setup("Execute", ItExpr.IsAny(), ItExpr.IsAny())
+ .Callback((string file, Settings settings) => executeAction(file));
return mock.Object;
}
diff --git a/src/modules/imageresizer/ui/Models/ResizeBatch.cs b/src/modules/imageresizer/ui/Models/ResizeBatch.cs
index 1181395c09..87e0b84e7b 100644
--- a/src/modules/imageresizer/ui/Models/ResizeBatch.cs
+++ b/src/modules/imageresizer/ui/Models/ResizeBatch.cs
@@ -87,9 +87,14 @@ namespace ImageResizer.Models
public IEnumerable Process(Action reportProgress, CancellationToken cancellationToken)
{
double total = Files.Count;
- var completed = 0;
+ int completed = 0;
var errors = new ConcurrentBag();
+ // NOTE: Settings.Default is captured once before parallel processing.
+ // Any changes to settings on disk during this batch will NOT be reflected until the next batch.
+ // This improves performance and predictability by avoiding repeated mutex acquisition and behaviour change results in a batch.
+ var settings = Settings.Default;
+
// TODO: If we ever switch to Windows.Graphics.Imaging, we can get a lot more throughput by using the async
// APIs and a custom SynchronizationContext
Parallel.ForEach(
@@ -97,13 +102,12 @@ namespace ImageResizer.Models
new ParallelOptions
{
CancellationToken = cancellationToken,
- MaxDegreeOfParallelism = Environment.ProcessorCount,
},
(file, state, i) =>
{
try
{
- Execute(file);
+ Execute(file, settings);
}
catch (Exception ex)
{
@@ -111,14 +115,13 @@ namespace ImageResizer.Models
}
Interlocked.Increment(ref completed);
-
reportProgress(completed, total);
});
return errors;
}
- protected virtual void Execute(string file)
- => new ResizeOperation(file, DestinationDirectory, Settings.Default).Execute();
+ protected virtual void Execute(string file, Settings settings)
+ => new ResizeOperation(file, DestinationDirectory, settings).Execute();
}
}
diff --git a/src/modules/imageresizer/ui/Properties/Settings.cs b/src/modules/imageresizer/ui/Properties/Settings.cs
index debb26a191..0f8690dcbb 100644
--- a/src/modules/imageresizer/ui/Properties/Settings.cs
+++ b/src/modules/imageresizer/ui/Properties/Settings.cs
@@ -461,33 +461,42 @@ namespace ImageResizer.Properties
{
}
- // Needs to be called on the App UI thread as the properties are bound to the UI.
- App.Current.Dispatcher.Invoke(() =>
+ if (App.Current?.Dispatcher != null)
{
- ShrinkOnly = jsonSettings.ShrinkOnly;
- Replace = jsonSettings.Replace;
- IgnoreOrientation = jsonSettings.IgnoreOrientation;
- RemoveMetadata = jsonSettings.RemoveMetadata;
- JpegQualityLevel = jsonSettings.JpegQualityLevel;
- PngInterlaceOption = jsonSettings.PngInterlaceOption;
- TiffCompressOption = jsonSettings.TiffCompressOption;
- FileName = jsonSettings.FileName;
- KeepDateModified = jsonSettings.KeepDateModified;
- FallbackEncoder = jsonSettings.FallbackEncoder;
- CustomSize = jsonSettings.CustomSize;
- SelectedSizeIndex = jsonSettings.SelectedSizeIndex;
-
- if (jsonSettings.Sizes.Count > 0)
- {
- Sizes.Clear();
- Sizes.AddRange(jsonSettings.Sizes);
-
- // Ensure Ids are unique and handle missing Ids
- IdRecoveryHelper.RecoverInvalidIds(Sizes);
- }
- });
+ // Needs to be called on the App UI thread as the properties are bound to the UI.
+ App.Current.Dispatcher.Invoke(() => ReloadCore(jsonSettings));
+ }
+ else
+ {
+ ReloadCore(jsonSettings);
+ }
_jsonMutex.ReleaseMutex();
}
+
+ private void ReloadCore(Settings jsonSettings)
+ {
+ ShrinkOnly = jsonSettings.ShrinkOnly;
+ Replace = jsonSettings.Replace;
+ IgnoreOrientation = jsonSettings.IgnoreOrientation;
+ RemoveMetadata = jsonSettings.RemoveMetadata;
+ JpegQualityLevel = jsonSettings.JpegQualityLevel;
+ PngInterlaceOption = jsonSettings.PngInterlaceOption;
+ TiffCompressOption = jsonSettings.TiffCompressOption;
+ FileName = jsonSettings.FileName;
+ KeepDateModified = jsonSettings.KeepDateModified;
+ FallbackEncoder = jsonSettings.FallbackEncoder;
+ CustomSize = jsonSettings.CustomSize;
+ SelectedSizeIndex = jsonSettings.SelectedSizeIndex;
+
+ if (jsonSettings.Sizes.Count > 0)
+ {
+ Sizes.Clear();
+ Sizes.AddRange(jsonSettings.Sizes);
+
+ // Ensure Ids are unique and handle missing Ids
+ IdRecoveryHelper.RecoverInvalidIds(Sizes);
+ }
+ }
}
}
diff --git a/src/modules/peek/Peek.FilePreviewer/Previewers/MediaPreviewer/AudioPreviewer.cs b/src/modules/peek/Peek.FilePreviewer/Previewers/MediaPreviewer/AudioPreviewer.cs
index 586b92ed75..a3721a04ec 100644
--- a/src/modules/peek/Peek.FilePreviewer/Previewers/MediaPreviewer/AudioPreviewer.cs
+++ b/src/modules/peek/Peek.FilePreviewer/Previewers/MediaPreviewer/AudioPreviewer.cs
@@ -24,13 +24,15 @@ using Windows.Storage;
namespace Peek.FilePreviewer.Previewers.MediaPreviewer
{
- public partial class AudioPreviewer : ObservableObject, IAudioPreviewer
+ public partial class AudioPreviewer : ObservableObject, IDisposable, IAudioPreviewer
{
+ private MediaSource? _mediaSource;
+
[ObservableProperty]
private PreviewState _state;
[ObservableProperty]
- private AudioPreviewData _preview;
+ private AudioPreviewData? _preview;
private IFileSystemItem Item { get; }
@@ -40,7 +42,6 @@ namespace Peek.FilePreviewer.Previewers.MediaPreviewer
{
Item = file;
Dispatcher = DispatcherQueue.GetForCurrentThread();
- Preview = new AudioPreviewData();
}
public async Task CopyAsync()
@@ -63,19 +64,23 @@ namespace Peek.FilePreviewer.Previewers.MediaPreviewer
{
State = PreviewState.Loading;
+ Preview = new AudioPreviewData();
+
var thumbnailTask = LoadThumbnailAsync(cancellationToken);
var sourceTask = LoadSourceAsync(cancellationToken);
var metadataTask = LoadMetadataAsync(cancellationToken);
await Task.WhenAll(thumbnailTask, sourceTask, metadataTask);
- if (!thumbnailTask.Result || !sourceTask.Result || !metadataTask.Result)
+ if (sourceTask.Result && metadataTask.Result)
{
- State = PreviewState.Error;
+ State = PreviewState.Loaded;
}
else
{
- State = PreviewState.Loaded;
+ // Release all resources on error.
+ Unload();
+ State = PreviewState.Error;
}
}
@@ -88,12 +93,15 @@ namespace Peek.FilePreviewer.Previewers.MediaPreviewer
{
cancellationToken.ThrowIfCancellationRequested();
- var thumbnail = await ThumbnailHelper.GetThumbnailAsync(Item.Path, cancellationToken)
- ?? await ThumbnailHelper.GetIconAsync(Item.Path, cancellationToken);
+ if (Preview != null)
+ {
+ var thumbnail = await ThumbnailHelper.GetThumbnailAsync(Item.Path, cancellationToken)
+ ?? await ThumbnailHelper.GetIconAsync(Item.Path, cancellationToken);
- cancellationToken.ThrowIfCancellationRequested();
+ cancellationToken.ThrowIfCancellationRequested();
- Preview.Thumbnail = thumbnail ?? new SvgImageSource(new Uri("ms-appx:///Assets/Peek/DefaultFileIcon.svg"));
+ Preview.Thumbnail = thumbnail ?? new SvgImageSource(new Uri("ms-appx:///Assets/Peek/DefaultFileIcon.svg"));
+ }
});
});
}
@@ -110,7 +118,11 @@ namespace Peek.FilePreviewer.Previewers.MediaPreviewer
{
cancellationToken.ThrowIfCancellationRequested();
- Preview.MediaSource = MediaSource.CreateFromStorageFile(storageFile);
+ if (Preview != null)
+ {
+ _mediaSource = MediaSource.CreateFromStorageFile(storageFile);
+ Preview.MediaSource = _mediaSource;
+ }
});
});
}
@@ -123,6 +135,11 @@ namespace Peek.FilePreviewer.Previewers.MediaPreviewer
await Dispatcher.RunOnUiThread(() =>
{
+ if (Preview == null)
+ {
+ return;
+ }
+
cancellationToken.ThrowIfCancellationRequested();
Preview.Title = PropertyStoreHelper.TryGetStringProperty(Item.Path, PropertyKey.MusicTitle)
?? Item.Name[..^Item.Extension.Length];
@@ -160,6 +177,22 @@ namespace Peek.FilePreviewer.Previewers.MediaPreviewer
return _supportedFileTypes.Contains(item.Extension);
}
+ public void Dispose()
+ {
+ Unload();
+ GC.SuppressFinalize(this);
+ }
+
+ ///
+ /// Explicitly unloads the preview and releases file resources.
+ ///
+ public void Unload()
+ {
+ _mediaSource?.Dispose();
+ _mediaSource = null;
+ Preview = null;
+ }
+
private static readonly HashSet _supportedFileTypes = new()
{
".aac",
diff --git a/src/modules/peek/Peek.FilePreviewer/Previewers/MediaPreviewer/VideoPreviewer.cs b/src/modules/peek/Peek.FilePreviewer/Previewers/MediaPreviewer/VideoPreviewer.cs
index b9de53e87b..061d3eca47 100644
--- a/src/modules/peek/Peek.FilePreviewer/Previewers/MediaPreviewer/VideoPreviewer.cs
+++ b/src/modules/peek/Peek.FilePreviewer/Previewers/MediaPreviewer/VideoPreviewer.cs
@@ -25,6 +25,8 @@ namespace Peek.FilePreviewer.Previewers
{
public partial class VideoPreviewer : ObservableObject, IVideoPreviewer, IDisposable
{
+ private MediaSource? _mediaSource;
+
[ObservableProperty]
private MediaSource? preview;
@@ -56,6 +58,7 @@ namespace Peek.FilePreviewer.Previewers
public void Dispose()
{
+ Unload();
GC.SuppressFinalize(this);
}
@@ -145,7 +148,8 @@ namespace Peek.FilePreviewer.Previewers
MissingCodecName = missingCodecName;
}
- Preview = MediaSource.CreateFromStorageFile(storageFile);
+ _mediaSource = MediaSource.CreateFromStorageFile(storageFile);
+ Preview = _mediaSource;
});
});
}
@@ -155,6 +159,16 @@ namespace Peek.FilePreviewer.Previewers
return !(VideoTask?.Result ?? true);
}
+ ///
+ /// Explicitly unloads the preview and releases file resources.
+ ///
+ public void Unload()
+ {
+ _mediaSource?.Dispose();
+ _mediaSource = null;
+ Preview = null;
+ }
+
private static readonly HashSet _supportedFileTypes = new()
{
".mp4", ".3g2", ".3gp", ".3gp2", ".3gpp", ".asf", ".avi", ".m2t", ".m2ts",
diff --git a/src/modules/peek/Peek.UITests/PeekFilePreviewTests.cs b/src/modules/peek/Peek.UITests/PeekFilePreviewTests.cs
index 36f2491fcf..fd57c444ca 100644
--- a/src/modules/peek/Peek.UITests/PeekFilePreviewTests.cs
+++ b/src/modules/peek/Peek.UITests/PeekFilePreviewTests.cs
@@ -9,6 +9,7 @@ using System.Drawing;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
+using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.PowerToys.UITest;
@@ -35,6 +36,105 @@ public class PeekFilePreviewTests : UITestBase
{
}
+ static PeekFilePreviewTests()
+ {
+ FixSettingsFileBeforeTests();
+ }
+
+ private static readonly JsonSerializerOptions IndentedJsonOptions = new() { WriteIndented = true };
+
+ private static void FixSettingsFileBeforeTests()
+ {
+ try
+ {
+ // Default Peek settings
+ string peekSettingsContent = @"{
+ ""name"": ""Peek"",
+ ""version"": ""1.0"",
+ ""properties"": {
+ ""ActivationShortcut"": {
+ ""win"": false,
+ ""ctrl"": true,
+ ""alt"": false,
+ ""shift"": false,
+ ""code"": 32,
+ ""key"": ""Space""
+ },
+ ""AlwaysRunNotElevated"": {
+ ""value"": true
+ },
+ ""CloseAfterLosingFocus"": {
+ ""value"": false
+ },
+ ""ConfirmFileDelete"": {
+ ""value"": true
+ },
+ ""EnableSpaceToActivate"": {
+ ""value"": false
+ }
+ }
+ }";
+
+ // Update Peek module settings
+ SettingsConfigHelper.UpdateModuleSettings(
+ "Peek",
+ peekSettingsContent,
+ (settings) =>
+ {
+ // Get or ensure properties section exists
+ Dictionary properties;
+
+ if (settings.TryGetValue("properties", out var propertiesObj))
+ {
+ if (propertiesObj is Dictionary dict)
+ {
+ properties = dict;
+ }
+ else if (propertiesObj is JsonElement jsonElem)
+ {
+ properties = JsonSerializer.Deserialize>(jsonElem.GetRawText())
+ ?? throw new InvalidOperationException("Failed to deserialize properties");
+ }
+ else
+ {
+ properties = new Dictionary();
+ }
+ }
+ else
+ {
+ properties = new Dictionary();
+ }
+
+ // Update the required properties
+ properties["ActivationShortcut"] = new Dictionary
+ {
+ { "win", false },
+ { "ctrl", true },
+ { "alt", false },
+ { "shift", false },
+ { "code", 32 },
+ { "key", "Space" },
+ };
+
+ properties["EnableSpaceToActivate"] = new Dictionary
+ {
+ { "value", false },
+ };
+
+ settings["properties"] = properties;
+ });
+
+ // Disable all modules except Peek in global settings
+ SettingsConfigHelper.ConfigureGlobalModuleSettings("Peek");
+
+ Debug.WriteLine("Successfully updated all settings - Peek shortcut configured and all modules except Peek disabled");
+ }
+ catch (Exception ex)
+ {
+ Assert.Fail($"ERROR in FixSettingsFileBeforeTests: {ex.Message}");
+ }
+ }
+
[TestInitialize]
public void TestInitialize()
{
diff --git a/src/modules/poweraccent/PowerAccent.Core/Languages.cs b/src/modules/poweraccent/PowerAccent.Core/Languages.cs
index 6e329ffe7f..06c3a2bea3 100644
--- a/src/modules/poweraccent/PowerAccent.Core/Languages.cs
+++ b/src/modules/poweraccent/PowerAccent.Core/Languages.cs
@@ -212,7 +212,7 @@ namespace PowerAccent.Core
LetterKey.VK_L => new[] { "ļ", "₺" }, // ₺ is in VK_T for other languages, but not VK_L, so we add it here.
LetterKey.VK_M => new[] { "ṁ" },
LetterKey.VK_N => new[] { "ņ", "ṅ", "ⁿ", "ℕ", "№" },
- LetterKey.VK_O => new[] { "ȯ", "∅" },
+ LetterKey.VK_O => new[] { "ȯ", "∅", "⌀" },
LetterKey.VK_P => new[] { "ṗ", "℗", "∏", "¶" },
LetterKey.VK_Q => new[] { "ℚ" },
LetterKey.VK_R => new[] { "ṙ", "®", "ℝ" },
diff --git a/src/modules/powerrename/PowerRename.FuzzingTest/PowerRename.FuzzingTest.vcxproj b/src/modules/powerrename/PowerRename.FuzzingTest/PowerRename.FuzzingTest.vcxproj
index 3afb41f546..0bfb8d2db1 100644
--- a/src/modules/powerrename/PowerRename.FuzzingTest/PowerRename.FuzzingTest.vcxproj
+++ b/src/modules/powerrename/PowerRename.FuzzingTest/PowerRename.FuzzingTest.vcxproj
@@ -71,6 +71,7 @@
Console
+ windowscodecs.lib;%(AdditionalDependencies)
diff --git a/src/runner/runner.vcxproj b/src/runner/runner.vcxproj
index afff599d8e..1eae5a3573 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/AIServiceTypeRegistry.cs b/src/settings-ui/Settings.UI.Library/AIServiceTypeRegistry.cs
index d08f2a1266..c967f5d840 100644
--- a/src/settings-ui/Settings.UI.Library/AIServiceTypeRegistry.cs
+++ b/src/settings-ui/Settings.UI.Library/AIServiceTypeRegistry.cs
@@ -56,9 +56,9 @@ public static class AIServiceTypeRegistry
IsOnlineService = true,
LegalDescription = "AdvancedPaste_Google_LegalDescription",
TermsLabel = "AdvancedPaste_Google_TermsLabel",
- TermsUri = new Uri("https://policies.google.com/terms"),
+ TermsUri = new Uri("https://ai.google.dev/gemini-api/terms"),
PrivacyLabel = "AdvancedPaste_Google_PrivacyLabel",
- PrivacyUri = new Uri("https://policies.google.com/privacy"),
+ PrivacyUri = new Uri("https://support.google.com/gemini/answer/13594961"),
},
[AIServiceType.Mistral] = new AIServiceTypeMetadata
{
@@ -93,9 +93,9 @@ public static class AIServiceTypeRegistry
IsLocalModel = true,
LegalDescription = "AdvancedPaste_LocalModel_LegalDescription",
TermsLabel = "AdvancedPaste_Ollama_TermsLabel",
- TermsUri = new Uri("https://ollama.com/terms"),
+ TermsUri = new Uri("https://ollama.org/terms"),
PrivacyLabel = "AdvancedPaste_Ollama_PrivacyLabel",
- PrivacyUri = new Uri("https://ollama.com/privacy"),
+ PrivacyUri = new Uri("https://ollama.org/privacy"),
},
[AIServiceType.Onnx] = new AIServiceTypeMetadata
{
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/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/SettingsXAML/Controls/Dashboard/ShortcutConflictControl.xaml b/src/settings-ui/Settings.UI/SettingsXAML/Controls/Dashboard/ShortcutConflictControl.xaml
index b071e7f6fe..44470ebbc1 100644
--- a/src/settings-ui/Settings.UI/SettingsXAML/Controls/Dashboard/ShortcutConflictControl.xaml
+++ b/src/settings-ui/Settings.UI/SettingsXAML/Controls/Dashboard/ShortcutConflictControl.xaml
@@ -9,7 +9,10 @@
mc:Ignorable="d">
-