mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-08 04:07:40 +02:00
8
.github/actions/spell-check/expect.txt
vendored
8
.github/actions/spell-check/expect.txt
vendored
@@ -66,6 +66,7 @@ APIIs
|
|||||||
Apm
|
Apm
|
||||||
APPBARDATA
|
APPBARDATA
|
||||||
APPEXECLINK
|
APPEXECLINK
|
||||||
|
appext
|
||||||
APPLICATIONFRAMEHOST
|
APPLICATIONFRAMEHOST
|
||||||
appmanifest
|
appmanifest
|
||||||
APPMODEL
|
APPMODEL
|
||||||
@@ -187,6 +188,7 @@ CAPTUREBLT
|
|||||||
CAPTURECHANGED
|
CAPTURECHANGED
|
||||||
CARETBLINKING
|
CARETBLINKING
|
||||||
CAtl
|
CAtl
|
||||||
|
CBN
|
||||||
cch
|
cch
|
||||||
CCHDEVICENAME
|
CCHDEVICENAME
|
||||||
CCHFORMNAME
|
CCHFORMNAME
|
||||||
@@ -414,6 +416,9 @@ DNLEN
|
|||||||
DONOTROUND
|
DONOTROUND
|
||||||
DONTVALIDATEPATH
|
DONTVALIDATEPATH
|
||||||
dotnet
|
dotnet
|
||||||
|
downsampled
|
||||||
|
downsampling
|
||||||
|
Downsampled
|
||||||
downscale
|
downscale
|
||||||
DPICHANGED
|
DPICHANGED
|
||||||
DPIs
|
DPIs
|
||||||
@@ -598,6 +603,7 @@ getfilesiginforedist
|
|||||||
geolocator
|
geolocator
|
||||||
GETHOTKEY
|
GETHOTKEY
|
||||||
GETICON
|
GETICON
|
||||||
|
GETLBTEXT
|
||||||
GETMINMAXINFO
|
GETMINMAXINFO
|
||||||
GETNONCLIENTMETRICS
|
GETNONCLIENTMETRICS
|
||||||
GETPROPERTYSTOREFLAGS
|
GETPROPERTYSTOREFLAGS
|
||||||
@@ -605,6 +611,7 @@ GETSCREENSAVERRUNNING
|
|||||||
GETSECKEY
|
GETSECKEY
|
||||||
GETSTICKYKEYS
|
GETSTICKYKEYS
|
||||||
GETTEXTLENGTH
|
GETTEXTLENGTH
|
||||||
|
GIFs
|
||||||
gitmodules
|
gitmodules
|
||||||
GHND
|
GHND
|
||||||
GMEM
|
GMEM
|
||||||
@@ -615,6 +622,7 @@ GPOCA
|
|||||||
gpp
|
gpp
|
||||||
gpu
|
gpu
|
||||||
gradians
|
gradians
|
||||||
|
grctlext
|
||||||
Gridcustomlayout
|
Gridcustomlayout
|
||||||
GSM
|
GSM
|
||||||
gtm
|
gtm
|
||||||
|
|||||||
548
src/modules/ZoomIt/ZoomIt/GifRecordingSession.cpp
Normal file
548
src/modules/ZoomIt/ZoomIt/GifRecordingSession.cpp
Normal file
@@ -0,0 +1,548 @@
|
|||||||
|
//==============================================================================
|
||||||
|
//
|
||||||
|
// Zoomit
|
||||||
|
// Sysinternals - www.sysinternals.com
|
||||||
|
//
|
||||||
|
// GIF recording support using Windows Imaging Component (WIC)
|
||||||
|
//
|
||||||
|
//==============================================================================
|
||||||
|
#include "pch.h"
|
||||||
|
#include "GifRecordingSession.h"
|
||||||
|
#include "CaptureFrameWait.h"
|
||||||
|
#include <shcore.h>
|
||||||
|
|
||||||
|
extern DWORD g_RecordScaling;
|
||||||
|
|
||||||
|
namespace winrt
|
||||||
|
{
|
||||||
|
using namespace Windows::Foundation;
|
||||||
|
using namespace Windows::Graphics;
|
||||||
|
using namespace Windows::Graphics::Capture;
|
||||||
|
using namespace Windows::Graphics::DirectX;
|
||||||
|
using namespace Windows::Graphics::DirectX::Direct3D11;
|
||||||
|
using namespace Windows::Storage;
|
||||||
|
using namespace Windows::UI::Composition;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace util
|
||||||
|
{
|
||||||
|
using namespace robmikh::common::uwp;
|
||||||
|
}
|
||||||
|
|
||||||
|
const float CLEAR_COLOR[] = { 0.0f, 0.0f, 0.0f, 1.0f };
|
||||||
|
|
||||||
|
int32_t EnsureEvenGif(int32_t value)
|
||||||
|
{
|
||||||
|
if (value % 2 == 0)
|
||||||
|
{
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return value + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// GifRecordingSession::GifRecordingSession
|
||||||
|
//
|
||||||
|
//----------------------------------------------------------------------------
|
||||||
|
GifRecordingSession::GifRecordingSession(
|
||||||
|
winrt::IDirect3DDevice const& device,
|
||||||
|
winrt::GraphicsCaptureItem const& item,
|
||||||
|
RECT const cropRect,
|
||||||
|
uint32_t frameRate,
|
||||||
|
winrt::Streams::IRandomAccessStream const& stream)
|
||||||
|
{
|
||||||
|
m_device = device;
|
||||||
|
m_d3dDevice = GetDXGIInterfaceFromObject<ID3D11Device>(m_device);
|
||||||
|
m_d3dDevice->GetImmediateContext(m_d3dContext.put());
|
||||||
|
m_item = item;
|
||||||
|
m_frameRate = frameRate;
|
||||||
|
m_stream = stream;
|
||||||
|
|
||||||
|
auto itemSize = item.Size();
|
||||||
|
auto inputWidth = EnsureEvenGif(itemSize.Width);
|
||||||
|
auto inputHeight = EnsureEvenGif(itemSize.Height);
|
||||||
|
m_frameWait = std::make_shared<CaptureFrameWait>(m_device, m_item, winrt::SizeInt32{ inputWidth, inputHeight });
|
||||||
|
auto weakPointer{ std::weak_ptr{ m_frameWait } };
|
||||||
|
m_itemClosed = item.Closed(winrt::auto_revoke, [weakPointer](auto&, auto&)
|
||||||
|
{
|
||||||
|
auto sharedPointer{ weakPointer.lock() };
|
||||||
|
if (sharedPointer)
|
||||||
|
{
|
||||||
|
sharedPointer->StopCapture();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get crop dimension
|
||||||
|
if ((cropRect.right - cropRect.left) != 0)
|
||||||
|
{
|
||||||
|
m_rcCrop = cropRect;
|
||||||
|
m_frameWait->ShowCaptureBorder(false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_rcCrop.left = 0;
|
||||||
|
m_rcCrop.top = 0;
|
||||||
|
m_rcCrop.right = inputWidth;
|
||||||
|
m_rcCrop.bottom = inputHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply scaling
|
||||||
|
constexpr int c_minimumSize = 34;
|
||||||
|
auto scaledWidth = MulDiv(m_rcCrop.right - m_rcCrop.left, g_RecordScaling, 100);
|
||||||
|
auto scaledHeight = MulDiv(m_rcCrop.bottom - m_rcCrop.top, g_RecordScaling, 100);
|
||||||
|
m_width = scaledWidth;
|
||||||
|
m_height = scaledHeight;
|
||||||
|
if (m_width < c_minimumSize)
|
||||||
|
{
|
||||||
|
m_width = c_minimumSize;
|
||||||
|
m_height = MulDiv(m_height, m_width, scaledWidth);
|
||||||
|
}
|
||||||
|
if (m_height < c_minimumSize)
|
||||||
|
{
|
||||||
|
m_height = c_minimumSize;
|
||||||
|
m_width = MulDiv(m_width, m_height, scaledHeight);
|
||||||
|
}
|
||||||
|
if (m_width > inputWidth)
|
||||||
|
{
|
||||||
|
m_width = inputWidth;
|
||||||
|
m_height = c_minimumSize, MulDiv(m_height, scaledWidth, m_width);
|
||||||
|
}
|
||||||
|
if (m_height > inputHeight)
|
||||||
|
{
|
||||||
|
m_height = inputHeight;
|
||||||
|
m_width = c_minimumSize, MulDiv(m_width, scaledHeight, m_height);
|
||||||
|
}
|
||||||
|
m_width = EnsureEvenGif(m_width);
|
||||||
|
m_height = EnsureEvenGif(m_height);
|
||||||
|
|
||||||
|
m_frameDelay = (frameRate > 0) ? (100 / frameRate) : 15;
|
||||||
|
|
||||||
|
// Initialize WIC
|
||||||
|
winrt::check_hresult(CoCreateInstance(
|
||||||
|
CLSID_WICImagingFactory,
|
||||||
|
nullptr,
|
||||||
|
CLSCTX_INPROC_SERVER,
|
||||||
|
IID_PPV_ARGS(m_wicFactory.put())));
|
||||||
|
|
||||||
|
// Create WIC stream from IRandomAccessStream
|
||||||
|
winrt::check_hresult(m_wicFactory->CreateStream(m_wicStream.put()));
|
||||||
|
|
||||||
|
// Get the IStream from the IRandomAccessStream
|
||||||
|
winrt::com_ptr<IStream> streamInterop;
|
||||||
|
winrt::check_hresult(CreateStreamOverRandomAccessStream(
|
||||||
|
winrt::get_unknown(stream),
|
||||||
|
IID_PPV_ARGS(streamInterop.put())));
|
||||||
|
winrt::check_hresult(m_wicStream->InitializeFromIStream(streamInterop.get()));
|
||||||
|
|
||||||
|
// Create GIF encoder
|
||||||
|
winrt::check_hresult(m_wicFactory->CreateEncoder(
|
||||||
|
GUID_ContainerFormatGif,
|
||||||
|
nullptr,
|
||||||
|
m_gifEncoder.put()));
|
||||||
|
|
||||||
|
winrt::check_hresult(m_gifEncoder->Initialize(m_wicStream.get(), WICBitmapEncoderNoCache));
|
||||||
|
|
||||||
|
// Set global GIF metadata for looping (NETSCAPE2.0 application extension)
|
||||||
|
try
|
||||||
|
{
|
||||||
|
winrt::com_ptr<IWICMetadataQueryWriter> encoderMetadataWriter;
|
||||||
|
if (SUCCEEDED(m_gifEncoder->GetMetadataQueryWriter(encoderMetadataWriter.put())) && encoderMetadataWriter)
|
||||||
|
{
|
||||||
|
OutputDebugStringW(L"Setting NETSCAPE2.0 looping extension on encoder...\n");
|
||||||
|
|
||||||
|
// Set application extension
|
||||||
|
PROPVARIANT propValue;
|
||||||
|
PropVariantInit(&propValue);
|
||||||
|
propValue.vt = VT_UI1 | VT_VECTOR;
|
||||||
|
propValue.caub.cElems = 11;
|
||||||
|
propValue.caub.pElems = static_cast<UCHAR*>(CoTaskMemAlloc(11));
|
||||||
|
if (propValue.caub.pElems != nullptr)
|
||||||
|
{
|
||||||
|
memcpy(propValue.caub.pElems, "NETSCAPE2.0", 11);
|
||||||
|
HRESULT hr = encoderMetadataWriter->SetMetadataByName(L"/appext/application", &propValue);
|
||||||
|
if (SUCCEEDED(hr))
|
||||||
|
{
|
||||||
|
OutputDebugStringW(L"Encoder application extension set successfully\n");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
OutputDebugStringW(L"Failed to set encoder application extension\n");
|
||||||
|
}
|
||||||
|
PropVariantClear(&propValue);
|
||||||
|
|
||||||
|
// Set loop count (0 = infinite)
|
||||||
|
PropVariantInit(&propValue);
|
||||||
|
propValue.vt = VT_UI1 | VT_VECTOR;
|
||||||
|
propValue.caub.cElems = 5;
|
||||||
|
propValue.caub.pElems = static_cast<UCHAR*>(CoTaskMemAlloc(5));
|
||||||
|
if (propValue.caub.pElems != nullptr)
|
||||||
|
{
|
||||||
|
propValue.caub.pElems[0] = 3;
|
||||||
|
propValue.caub.pElems[1] = 1;
|
||||||
|
propValue.caub.pElems[2] = 0;
|
||||||
|
propValue.caub.pElems[3] = 0;
|
||||||
|
propValue.caub.pElems[4] = 0;
|
||||||
|
hr = encoderMetadataWriter->SetMetadataByName(L"/appext/data", &propValue);
|
||||||
|
if (SUCCEEDED(hr))
|
||||||
|
{
|
||||||
|
OutputDebugStringW(L"Encoder loop count set successfully\n");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
OutputDebugStringW(L"Failed to set encoder loop count\n");
|
||||||
|
}
|
||||||
|
PropVariantClear(&propValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
OutputDebugStringW(L"Failed to get encoder metadata writer\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (...)
|
||||||
|
{
|
||||||
|
OutputDebugStringW(L"Warning: Failed to set GIF encoder looping metadata\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// GifRecordingSession::~GifRecordingSession
|
||||||
|
//
|
||||||
|
//----------------------------------------------------------------------------
|
||||||
|
GifRecordingSession::~GifRecordingSession()
|
||||||
|
{
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// GifRecordingSession::Create
|
||||||
|
//
|
||||||
|
//----------------------------------------------------------------------------
|
||||||
|
std::shared_ptr<GifRecordingSession> GifRecordingSession::Create(
|
||||||
|
winrt::IDirect3DDevice const& device,
|
||||||
|
winrt::GraphicsCaptureItem const& item,
|
||||||
|
RECT const& crop,
|
||||||
|
uint32_t frameRate,
|
||||||
|
winrt::Streams::IRandomAccessStream const& stream)
|
||||||
|
{
|
||||||
|
return std::shared_ptr<GifRecordingSession>(new GifRecordingSession(device, item, crop, frameRate, stream));
|
||||||
|
}
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// GifRecordingSession::EncodeFrame
|
||||||
|
//
|
||||||
|
//----------------------------------------------------------------------------
|
||||||
|
HRESULT GifRecordingSession::EncodeFrame(ID3D11Texture2D* frameTexture)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Create a staging texture for CPU access
|
||||||
|
D3D11_TEXTURE2D_DESC frameDesc;
|
||||||
|
frameTexture->GetDesc(&frameDesc);
|
||||||
|
|
||||||
|
// GIF encoding with palette generation is VERY slow at high resolutions (4K takes 1 second per frame!)
|
||||||
|
|
||||||
|
UINT targetWidth = frameDesc.Width;
|
||||||
|
UINT targetHeight = frameDesc.Height;
|
||||||
|
|
||||||
|
if (frameDesc.Width > static_cast<uint32_t>(m_width) || frameDesc.Height > static_cast<uint32_t>(m_height))
|
||||||
|
{
|
||||||
|
float scaleX = static_cast<float>(m_width) / frameDesc.Width;
|
||||||
|
float scaleY = static_cast<float>(m_height) / frameDesc.Height;
|
||||||
|
float scale = min(scaleX, scaleY);
|
||||||
|
|
||||||
|
targetWidth = static_cast<UINT>(frameDesc.Width * scale);
|
||||||
|
targetHeight = static_cast<UINT>(frameDesc.Height * scale);
|
||||||
|
|
||||||
|
// Ensure even dimensions for GIF
|
||||||
|
targetWidth = (targetWidth / 2) * 2;
|
||||||
|
targetHeight = (targetHeight / 2) * 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
D3D11_TEXTURE2D_DESC stagingDesc = frameDesc;
|
||||||
|
stagingDesc.Usage = D3D11_USAGE_STAGING;
|
||||||
|
stagingDesc.BindFlags = 0;
|
||||||
|
stagingDesc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
|
||||||
|
stagingDesc.MiscFlags = 0;
|
||||||
|
|
||||||
|
winrt::com_ptr<ID3D11Texture2D> stagingTexture;
|
||||||
|
winrt::check_hresult(m_d3dDevice->CreateTexture2D(&stagingDesc, nullptr, stagingTexture.put()));
|
||||||
|
|
||||||
|
// Copy the frame to staging texture
|
||||||
|
m_d3dContext->CopyResource(stagingTexture.get(), frameTexture);
|
||||||
|
|
||||||
|
// Map the staging texture
|
||||||
|
D3D11_MAPPED_SUBRESOURCE mappedResource;
|
||||||
|
winrt::check_hresult(m_d3dContext->Map(stagingTexture.get(), 0, D3D11_MAP_READ, 0, &mappedResource));
|
||||||
|
|
||||||
|
// Create a new frame in the GIF
|
||||||
|
winrt::com_ptr<IWICBitmapFrameEncode> frameEncode;
|
||||||
|
winrt::com_ptr<IPropertyBag2> propertyBag;
|
||||||
|
winrt::check_hresult(m_gifEncoder->CreateNewFrame(frameEncode.put(), propertyBag.put()));
|
||||||
|
|
||||||
|
// Initialize the frame encoder with property bag
|
||||||
|
winrt::check_hresult(frameEncode->Initialize(propertyBag.get()));
|
||||||
|
|
||||||
|
// CRITICAL: For GIF, we MUST set size and pixel format BEFORE WriteSource
|
||||||
|
// Use target dimensions (may be downsampled)
|
||||||
|
winrt::check_hresult(frameEncode->SetSize(targetWidth, targetHeight));
|
||||||
|
|
||||||
|
// Set the pixel format to 8-bit indexed (required for GIF)
|
||||||
|
WICPixelFormatGUID pixelFormat = GUID_WICPixelFormat8bppIndexed;
|
||||||
|
winrt::check_hresult(frameEncode->SetPixelFormat(&pixelFormat));
|
||||||
|
|
||||||
|
// Create a WIC bitmap from the BGRA texture data
|
||||||
|
winrt::com_ptr<IWICBitmap> sourceBitmap;
|
||||||
|
winrt::check_hresult(m_wicFactory->CreateBitmapFromMemory(
|
||||||
|
frameDesc.Width,
|
||||||
|
frameDesc.Height,
|
||||||
|
GUID_WICPixelFormat32bppBGRA,
|
||||||
|
mappedResource.RowPitch,
|
||||||
|
frameDesc.Height * mappedResource.RowPitch,
|
||||||
|
static_cast<BYTE*>(mappedResource.pData),
|
||||||
|
sourceBitmap.put()));
|
||||||
|
|
||||||
|
// If we need downsampling, use WIC scaler
|
||||||
|
winrt::com_ptr<IWICBitmapSource> finalSource = sourceBitmap;
|
||||||
|
if (targetWidth != frameDesc.Width || targetHeight != frameDesc.Height)
|
||||||
|
{
|
||||||
|
winrt::com_ptr<IWICBitmapScaler> scaler;
|
||||||
|
winrt::check_hresult(m_wicFactory->CreateBitmapScaler(scaler.put()));
|
||||||
|
winrt::check_hresult(scaler->Initialize(
|
||||||
|
sourceBitmap.get(),
|
||||||
|
targetWidth,
|
||||||
|
targetHeight,
|
||||||
|
WICBitmapInterpolationModeHighQualityCubic));
|
||||||
|
finalSource = scaler;
|
||||||
|
|
||||||
|
OutputDebugStringW((L"Downsampled from " + std::to_wstring(frameDesc.Width) + L"x" + std::to_wstring(frameDesc.Height) +
|
||||||
|
L" to " + std::to_wstring(targetWidth) + L"x" + std::to_wstring(targetHeight) + L"\n").c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use WriteSource - WIC will handle the BGRA to 8bpp indexed conversion
|
||||||
|
winrt::check_hresult(frameEncode->WriteSource(finalSource.get(), nullptr));
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
winrt::com_ptr<IWICMetadataQueryWriter> frameMetadataWriter;
|
||||||
|
if (SUCCEEDED(frameEncode->GetMetadataQueryWriter(frameMetadataWriter.put())) && frameMetadataWriter)
|
||||||
|
{
|
||||||
|
// Set the frame delay in the metadata (in hundredths of a second)
|
||||||
|
PROPVARIANT propValue;
|
||||||
|
PropVariantInit(&propValue);
|
||||||
|
propValue.vt = VT_UI2;
|
||||||
|
propValue.uiVal = static_cast<USHORT>(m_frameDelay);
|
||||||
|
frameMetadataWriter->SetMetadataByName(L"/grctlext/Delay", &propValue);
|
||||||
|
PropVariantClear(&propValue);
|
||||||
|
|
||||||
|
// Set disposal method (2 = restore to background, needed for animation)
|
||||||
|
PropVariantInit(&propValue);
|
||||||
|
propValue.vt = VT_UI1;
|
||||||
|
propValue.bVal = 2; // Disposal method: restore to background color
|
||||||
|
frameMetadataWriter->SetMetadataByName(L"/grctlext/Disposal", &propValue);
|
||||||
|
PropVariantClear(&propValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (...)
|
||||||
|
{
|
||||||
|
// Metadata setting failed, continue anyway
|
||||||
|
OutputDebugStringW(L"Warning: Failed to set GIF frame metadata\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Commit the frame
|
||||||
|
OutputDebugStringW(L"About to commit frame to encoder...\n");
|
||||||
|
winrt::check_hresult(frameEncode->Commit());
|
||||||
|
OutputDebugStringW(L"Frame committed successfully\n");
|
||||||
|
|
||||||
|
// Unmap the staging texture
|
||||||
|
m_d3dContext->Unmap(stagingTexture.get(), 0);
|
||||||
|
|
||||||
|
// Increment and log frame count
|
||||||
|
m_frameCount++;
|
||||||
|
OutputDebugStringW((L"GIF Frame #" + std::to_wstring(m_frameCount) + L" fully encoded and committed\n").c_str());
|
||||||
|
|
||||||
|
return S_OK;
|
||||||
|
}
|
||||||
|
catch (const winrt::hresult_error& error)
|
||||||
|
{
|
||||||
|
OutputDebugStringW(error.message().c_str());
|
||||||
|
return error.code();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// GifRecordingSession::StartAsync
|
||||||
|
//
|
||||||
|
//----------------------------------------------------------------------------
|
||||||
|
winrt::IAsyncAction GifRecordingSession::StartAsync()
|
||||||
|
{
|
||||||
|
auto expected = false;
|
||||||
|
if (m_isRecording.compare_exchange_strong(expected, true))
|
||||||
|
{
|
||||||
|
auto self = shared_from_this();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Start capturing frames
|
||||||
|
auto frameStartTime = std::chrono::high_resolution_clock::now();
|
||||||
|
int captureAttempts = 0;
|
||||||
|
int successfulCaptures = 0;
|
||||||
|
int duplicatedFrames = 0;
|
||||||
|
|
||||||
|
// Keep track of the last frame to duplicate when needed
|
||||||
|
winrt::com_ptr<ID3D11Texture2D> lastCroppedTexture;
|
||||||
|
|
||||||
|
while (m_isRecording && !m_closed)
|
||||||
|
{
|
||||||
|
captureAttempts++;
|
||||||
|
auto frame = m_frameWait->TryGetNextFrame();
|
||||||
|
|
||||||
|
winrt::com_ptr<ID3D11Texture2D> croppedTexture;
|
||||||
|
|
||||||
|
if (frame)
|
||||||
|
{
|
||||||
|
successfulCaptures++;
|
||||||
|
auto contentSize = frame->ContentSize;
|
||||||
|
auto frameTexture = GetDXGIInterfaceFromObject<ID3D11Texture2D>(frame->FrameTexture);
|
||||||
|
D3D11_TEXTURE2D_DESC desc = {};
|
||||||
|
frameTexture->GetDesc(&desc);
|
||||||
|
|
||||||
|
// Use the smaller of the crop size or content size
|
||||||
|
auto width = min(m_rcCrop.right - m_rcCrop.left, contentSize.Width);
|
||||||
|
auto height = min(m_rcCrop.bottom - m_rcCrop.top, contentSize.Height);
|
||||||
|
|
||||||
|
D3D11_TEXTURE2D_DESC croppedDesc = {};
|
||||||
|
croppedDesc.Width = width;
|
||||||
|
croppedDesc.Height = height;
|
||||||
|
croppedDesc.MipLevels = 1;
|
||||||
|
croppedDesc.ArraySize = 1;
|
||||||
|
croppedDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
|
||||||
|
croppedDesc.SampleDesc.Count = 1;
|
||||||
|
croppedDesc.Usage = D3D11_USAGE_DEFAULT;
|
||||||
|
croppedDesc.BindFlags = D3D11_BIND_RENDER_TARGET;
|
||||||
|
|
||||||
|
winrt::check_hresult(m_d3dDevice->CreateTexture2D(&croppedDesc, nullptr, croppedTexture.put()));
|
||||||
|
|
||||||
|
// Set the content region to copy and clamp the coordinates
|
||||||
|
D3D11_BOX region = {};
|
||||||
|
region.left = std::clamp(m_rcCrop.left, static_cast<LONG>(0), static_cast<LONG>(desc.Width));
|
||||||
|
region.right = std::clamp(m_rcCrop.left + width, static_cast<LONG>(0), static_cast<LONG>(desc.Width));
|
||||||
|
region.top = std::clamp(m_rcCrop.top, static_cast<LONG>(0), static_cast<LONG>(desc.Height));
|
||||||
|
region.bottom = std::clamp(m_rcCrop.top + height, static_cast<LONG>(0), static_cast<LONG>(desc.Height));
|
||||||
|
region.back = 1;
|
||||||
|
|
||||||
|
// Copy the cropped region
|
||||||
|
m_d3dContext->CopySubresourceRegion(
|
||||||
|
croppedTexture.get(),
|
||||||
|
0,
|
||||||
|
0, 0, 0,
|
||||||
|
frameTexture.get(),
|
||||||
|
0,
|
||||||
|
®ion);
|
||||||
|
|
||||||
|
// Save this as the last frame for duplication
|
||||||
|
lastCroppedTexture = croppedTexture;
|
||||||
|
}
|
||||||
|
else if (lastCroppedTexture)
|
||||||
|
{
|
||||||
|
// No new frame, duplicate the last one
|
||||||
|
duplicatedFrames++;
|
||||||
|
croppedTexture = lastCroppedTexture;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode the frame (either new or duplicated)
|
||||||
|
if (croppedTexture)
|
||||||
|
{
|
||||||
|
HRESULT hr = EncodeFrame(croppedTexture.get());
|
||||||
|
if (FAILED(hr))
|
||||||
|
{
|
||||||
|
CloseInternal();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for the next frame interval
|
||||||
|
co_await winrt::resume_after(std::chrono::milliseconds(1000 / m_frameRate));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Commit the GIF encoder
|
||||||
|
if (m_gifEncoder)
|
||||||
|
{
|
||||||
|
auto frameEndTime = std::chrono::high_resolution_clock::now();
|
||||||
|
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(frameEndTime - frameStartTime).count();
|
||||||
|
|
||||||
|
OutputDebugStringW(L"Recording stopped. Committing GIF encoder...\n");
|
||||||
|
OutputDebugStringW((L"Total frames captured: " + std::to_wstring(m_frameCount) + L"\n").c_str());
|
||||||
|
OutputDebugStringW((L"Capture attempts: " + std::to_wstring(captureAttempts) + L"\n").c_str());
|
||||||
|
OutputDebugStringW((L"Successful captures: " + std::to_wstring(successfulCaptures) + L"\n").c_str());
|
||||||
|
OutputDebugStringW((L"Duplicated frames: " + std::to_wstring(duplicatedFrames) + L"\n").c_str());
|
||||||
|
OutputDebugStringW((L"Recording duration: " + std::to_wstring(duration) + L"ms\n").c_str());
|
||||||
|
OutputDebugStringW((L"Actual FPS: " + std::to_wstring(m_frameCount * 1000.0 / duration) + L"\n").c_str());
|
||||||
|
|
||||||
|
winrt::check_hresult(m_gifEncoder->Commit());
|
||||||
|
OutputDebugStringW(L"GIF encoder committed successfully\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (const winrt::hresult_error& error)
|
||||||
|
{
|
||||||
|
OutputDebugStringW(L"Error in GIF recording: ");
|
||||||
|
OutputDebugStringW(error.message().c_str());
|
||||||
|
OutputDebugStringW(L"\n");
|
||||||
|
|
||||||
|
// Try to commit the encoder even on error
|
||||||
|
if (m_gifEncoder)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
m_gifEncoder->Commit();
|
||||||
|
}
|
||||||
|
catch (...) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
CloseInternal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
co_return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// GifRecordingSession::Close
|
||||||
|
//
|
||||||
|
//----------------------------------------------------------------------------
|
||||||
|
void GifRecordingSession::Close()
|
||||||
|
{
|
||||||
|
auto expected = false;
|
||||||
|
if (m_closed.compare_exchange_strong(expected, true))
|
||||||
|
{
|
||||||
|
expected = true;
|
||||||
|
if (!m_isRecording.compare_exchange_strong(expected, false))
|
||||||
|
{
|
||||||
|
CloseInternal();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_frameWait->StopCapture();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// GifRecordingSession::CloseInternal
|
||||||
|
//
|
||||||
|
//----------------------------------------------------------------------------
|
||||||
|
void GifRecordingSession::CloseInternal()
|
||||||
|
{
|
||||||
|
m_frameWait->StopCapture();
|
||||||
|
m_itemClosed.revoke();
|
||||||
|
}
|
||||||
69
src/modules/ZoomIt/ZoomIt/GifRecordingSession.h
Normal file
69
src/modules/ZoomIt/ZoomIt/GifRecordingSession.h
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
//==============================================================================
|
||||||
|
//
|
||||||
|
// Zoomit
|
||||||
|
// Sysinternals - www.sysinternals.com
|
||||||
|
//
|
||||||
|
// GIF recording support using Windows Imaging Component (WIC)
|
||||||
|
//
|
||||||
|
//==============================================================================
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CaptureFrameWait.h"
|
||||||
|
#include <d3d11_4.h>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
class GifRecordingSession : public std::enable_shared_from_this<GifRecordingSession>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
[[nodiscard]] static std::shared_ptr<GifRecordingSession> Create(
|
||||||
|
winrt::Direct3D11::IDirect3DDevice const& device,
|
||||||
|
winrt::GraphicsCaptureItem const& item,
|
||||||
|
RECT const& cropRect,
|
||||||
|
uint32_t frameRate,
|
||||||
|
winrt::Streams::IRandomAccessStream const& stream);
|
||||||
|
~GifRecordingSession();
|
||||||
|
|
||||||
|
winrt::IAsyncAction StartAsync();
|
||||||
|
void EnableCursorCapture(bool enable = true) { m_frameWait->EnableCursorCapture(enable); }
|
||||||
|
void Close();
|
||||||
|
|
||||||
|
private:
|
||||||
|
GifRecordingSession(
|
||||||
|
winrt::Direct3D11::IDirect3DDevice const& device,
|
||||||
|
winrt::Capture::GraphicsCaptureItem const& item,
|
||||||
|
RECT const cropRect,
|
||||||
|
uint32_t frameRate,
|
||||||
|
winrt::Streams::IRandomAccessStream const& stream);
|
||||||
|
void CloseInternal();
|
||||||
|
HRESULT EncodeFrame(ID3D11Texture2D* texture);
|
||||||
|
|
||||||
|
private:
|
||||||
|
winrt::Direct3D11::IDirect3DDevice m_device{ nullptr };
|
||||||
|
winrt::com_ptr<ID3D11Device> m_d3dDevice;
|
||||||
|
winrt::com_ptr<ID3D11DeviceContext> m_d3dContext;
|
||||||
|
RECT m_rcCrop;
|
||||||
|
uint32_t m_frameRate;
|
||||||
|
|
||||||
|
winrt::GraphicsCaptureItem m_item{ nullptr };
|
||||||
|
winrt::GraphicsCaptureItem::Closed_revoker m_itemClosed;
|
||||||
|
std::shared_ptr<CaptureFrameWait> m_frameWait;
|
||||||
|
|
||||||
|
winrt::Streams::IRandomAccessStream m_stream{ nullptr };
|
||||||
|
|
||||||
|
// WIC components for GIF encoding
|
||||||
|
winrt::com_ptr<IWICImagingFactory> m_wicFactory;
|
||||||
|
winrt::com_ptr<IWICStream> m_wicStream;
|
||||||
|
winrt::com_ptr<IWICBitmapEncoder> m_gifEncoder;
|
||||||
|
winrt::com_ptr<IWICMetadataQueryWriter> m_encoderMetadataWriter;
|
||||||
|
|
||||||
|
std::atomic<bool> m_isRecording = false;
|
||||||
|
std::atomic<bool> m_closed = false;
|
||||||
|
|
||||||
|
uint32_t m_frameWidth=0;
|
||||||
|
uint32_t m_frameHeight=0;
|
||||||
|
uint32_t m_frameDelay=0;
|
||||||
|
uint32_t m_frameCount = 0;
|
||||||
|
|
||||||
|
int32_t m_width=0;
|
||||||
|
int32_t m_height=0;
|
||||||
|
};
|
||||||
@@ -121,8 +121,8 @@ FONT 8, "MS Shell Dlg", 0, 0, 0x0
|
|||||||
BEGIN
|
BEGIN
|
||||||
DEFPUSHBUTTON "OK",IDOK,166,306,50,14
|
DEFPUSHBUTTON "OK",IDOK,166,306,50,14
|
||||||
PUSHBUTTON "Cancel",IDCANCEL,223,306,50,14
|
PUSHBUTTON "Cancel",IDCANCEL,223,306,50,14
|
||||||
LTEXT "ZoomIt v9.10",IDC_VERSION,42,7,73,10
|
LTEXT "ZoomIt v9.20",IDC_VERSION,42,7,73,10
|
||||||
LTEXT "Copyright © 2006-2025 Mark Russinovich",IDC_COPYRIGHT,42,17,231,8
|
LTEXT "Copyright <EFBFBD> 2006-2025 Mark Russinovich",IDC_COPYRIGHT,42,17,231,8
|
||||||
CONTROL "<a HREF=""https://www.sysinternals.com"">Sysinternals - www.sysinternals.com</a>",IDC_LINK,
|
CONTROL "<a HREF=""https://www.sysinternals.com"">Sysinternals - www.sysinternals.com</a>",IDC_LINK,
|
||||||
"SysLink",WS_TABSTOP,42,26,150,9
|
"SysLink",WS_TABSTOP,42,26,150,9
|
||||||
ICON "APPICON",IDC_STATIC,12,9,20,20
|
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 "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
|
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
|
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
|
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
|
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 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
|
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
|
CONTROL "&Capture audio input:",IDC_CAPTURE_AUDIO,"Button",BS_AUTOCHECKBOX | BS_LEFTTEXT | WS_TABSTOP,7,149,83,10
|
||||||
COMBOBOX IDC_MICROPHONE,81,152,172,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
|
COMBOBOX IDC_MICROPHONE,81,164,172,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
|
||||||
LTEXT "Microphone:",IDC_STATIC,32,154,47,8
|
LTEXT "Microphone:",IDC_STATIC,32,166,47,8
|
||||||
END
|
END
|
||||||
|
|
||||||
SNIP DIALOGEX 0, 0, 260, 68
|
SNIP DIALOGEX 0, 0, 260, 68
|
||||||
|
|||||||
@@ -234,6 +234,7 @@
|
|||||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
|
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
|
||||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
|
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="GifRecordingSession.cpp" />
|
||||||
<ClCompile Include="pch.cpp" />
|
<ClCompile Include="pch.cpp" />
|
||||||
<ClCompile Include="SelectRectangle.cpp">
|
<ClCompile Include="SelectRectangle.cpp">
|
||||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Use</PrecompiledHeader>
|
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Use</PrecompiledHeader>
|
||||||
@@ -288,6 +289,7 @@
|
|||||||
<ClInclude Include="AudioSampleGenerator.h" />
|
<ClInclude Include="AudioSampleGenerator.h" />
|
||||||
<ClInclude Include="$(MSBuildThisFileDirectory)..\..\..\common\sysinternals\Eula\Eula.h" />
|
<ClInclude Include="$(MSBuildThisFileDirectory)..\..\..\common\sysinternals\Eula\Eula.h" />
|
||||||
<ClInclude Include="$(MSBuildThisFileDirectory)..\ZoomItModuleInterface\Trace.h" />
|
<ClInclude Include="$(MSBuildThisFileDirectory)..\ZoomItModuleInterface\Trace.h" />
|
||||||
|
<ClInclude Include="GifRecordingSession.h" />
|
||||||
<ClInclude Include="pch.h" />
|
<ClInclude Include="pch.h" />
|
||||||
<ClInclude Include="Registry.h" />
|
<ClInclude Include="Registry.h" />
|
||||||
<ClInclude Include="resource.h" />
|
<ClInclude Include="resource.h" />
|
||||||
|
|||||||
@@ -54,6 +54,9 @@
|
|||||||
<ClCompile Include="$(MSBuildThisFileDirectory)..\..\..\common\sysinternals\WindowsVersions.cpp">
|
<ClCompile Include="$(MSBuildThisFileDirectory)..\..\..\common\sysinternals\WindowsVersions.cpp">
|
||||||
<Filter>Source Files</Filter>
|
<Filter>Source Files</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="GifRecordingSession.cpp">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="Registry.h">
|
<ClInclude Include="Registry.h">
|
||||||
@@ -95,6 +98,9 @@
|
|||||||
<ClInclude Include="ZoomItSettings.h">
|
<ClInclude Include="ZoomItSettings.h">
|
||||||
<Filter>Header Files</Filter>
|
<Filter>Header Files</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
<ClInclude Include="GifRecordingSession.h">
|
||||||
|
<Filter>Header Files</Filter>
|
||||||
|
</ClInclude>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Image Include="appicon.ico">
|
<Image Include="appicon.ico">
|
||||||
|
|||||||
@@ -3,6 +3,13 @@
|
|||||||
#include "Registry.h"
|
#include "Registry.h"
|
||||||
#include "DemoType.h"
|
#include "DemoType.h"
|
||||||
|
|
||||||
|
// Recording format enum
|
||||||
|
enum class RecordingFormat
|
||||||
|
{
|
||||||
|
GIF = 0,
|
||||||
|
MP4 = 1
|
||||||
|
};
|
||||||
|
|
||||||
DWORD g_ToggleKey = (HOTKEYF_CONTROL << 8)| '1';
|
DWORD g_ToggleKey = (HOTKEYF_CONTROL << 8)| '1';
|
||||||
DWORD g_LiveZoomToggleKey = ((HOTKEYF_CONTROL) << 8)| '4';
|
DWORD g_LiveZoomToggleKey = ((HOTKEYF_CONTROL) << 8)| '4';
|
||||||
DWORD g_DrawToggleKey = ((HOTKEYF_CONTROL) << 8)| '2';
|
DWORD g_DrawToggleKey = ((HOTKEYF_CONTROL) << 8)| '2';
|
||||||
@@ -38,8 +45,10 @@ BOOLEAN g_DemoTypeUserDriven = false;
|
|||||||
TCHAR g_DemoTypeFile[MAX_PATH] = {0};
|
TCHAR g_DemoTypeFile[MAX_PATH] = {0};
|
||||||
DWORD g_DemoTypeSpeedSlider = static_cast<int>(((MIN_TYPING_SPEED - MAX_TYPING_SPEED) / 2) + MAX_TYPING_SPEED);
|
DWORD g_DemoTypeSpeedSlider = static_cast<int>(((MIN_TYPING_SPEED - MAX_TYPING_SPEED) / 2) + MAX_TYPING_SPEED);
|
||||||
DWORD g_RecordFrameRate = 30;
|
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;
|
BOOLEAN g_CaptureAudio = FALSE;
|
||||||
TCHAR g_MicrophoneDeviceId[MAX_PATH] = {0};
|
TCHAR g_MicrophoneDeviceId[MAX_PATH] = {0};
|
||||||
|
|
||||||
@@ -79,7 +88,9 @@ REG_SETTING RegSettings[] = {
|
|||||||
{ L"ZoominSliderLevel", SETTING_TYPE_DWORD, 0, &g_SliderZoomLevel, static_cast<DOUBLE>(g_SliderZoomLevel) },
|
{ L"ZoominSliderLevel", SETTING_TYPE_DWORD, 0, &g_SliderZoomLevel, static_cast<DOUBLE>(g_SliderZoomLevel) },
|
||||||
{ L"Font", SETTING_TYPE_BINARY, sizeof g_LogFont, &g_LogFont, static_cast<DOUBLE>(0) },
|
{ L"Font", SETTING_TYPE_BINARY, sizeof g_LogFont, &g_LogFont, static_cast<DOUBLE>(0) },
|
||||||
{ L"RecordFrameRate", SETTING_TYPE_DWORD, 0, &g_RecordFrameRate, static_cast<DOUBLE>(g_RecordFrameRate) },
|
{ L"RecordFrameRate", SETTING_TYPE_DWORD, 0, &g_RecordFrameRate, static_cast<DOUBLE>(g_RecordFrameRate) },
|
||||||
{ L"RecordScaling", SETTING_TYPE_DWORD, 0, &g_RecordScaling, static_cast<DOUBLE>(g_RecordScaling) },
|
{ L"RecordingFormat", SETTING_TYPE_DWORD, 0, &g_RecordingFormat, static_cast<DOUBLE>(0) },
|
||||||
|
{ L"RecordScalingGIF", SETTING_TYPE_DWORD, 0, &g_RecordScalingGIF, static_cast<DOUBLE>(g_RecordScalingGIF) },
|
||||||
|
{ L"RecordScalingMP4", SETTING_TYPE_DWORD, 0, &g_RecordScalingMP4, static_cast<DOUBLE>(g_RecordScalingMP4) },
|
||||||
{ L"CaptureAudio", SETTING_TYPE_BOOLEAN, 0, &g_CaptureAudio, static_cast<DOUBLE>(g_CaptureAudio) },
|
{ L"CaptureAudio", SETTING_TYPE_BOOLEAN, 0, &g_CaptureAudio, static_cast<DOUBLE>(g_CaptureAudio) },
|
||||||
{ L"MicrophoneDeviceId", SETTING_TYPE_STRING, sizeof(g_MicrophoneDeviceId), g_MicrophoneDeviceId, static_cast<DOUBLE>(0) },
|
{ L"MicrophoneDeviceId", SETTING_TYPE_STRING, sizeof(g_MicrophoneDeviceId), g_MicrophoneDeviceId, static_cast<DOUBLE>(0) },
|
||||||
{ NULL, SETTING_TYPE_DWORD, 0, NULL, static_cast<DOUBLE>(0) }
|
{ NULL, SETTING_TYPE_DWORD, 0, NULL, static_cast<DOUBLE>(0) }
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
#include "Utility.h"
|
#include "Utility.h"
|
||||||
#include "WindowsVersions.h"
|
#include "WindowsVersions.h"
|
||||||
#include "ZoomItSettings.h"
|
#include "ZoomItSettings.h"
|
||||||
|
#include "GifRecordingSession.h"
|
||||||
|
|
||||||
#ifdef __ZOOMIT_POWERTOYS__
|
#ifdef __ZOOMIT_POWERTOYS__
|
||||||
#include <common/interop/shared_constants.h>
|
#include <common/interop/shared_constants.h>
|
||||||
@@ -68,6 +69,8 @@ COLORREF g_CustomColors[16];
|
|||||||
#define SNIP_SAVE_HOTKEY 9
|
#define SNIP_SAVE_HOTKEY 9
|
||||||
#define DEMOTYPE_HOTKEY 10
|
#define DEMOTYPE_HOTKEY 10
|
||||||
#define DEMOTYPE_RESET_HOTKEY 11
|
#define DEMOTYPE_RESET_HOTKEY 11
|
||||||
|
#define RECORD_GIF_HOTKEY 12
|
||||||
|
#define RECORD_GIF_WINDOW_HOTKEY 13
|
||||||
|
|
||||||
#define ZOOM_PAGE 0
|
#define ZOOM_PAGE 0
|
||||||
#define LIVE_PAGE 1
|
#define LIVE_PAGE 1
|
||||||
@@ -89,6 +92,11 @@ OPTION_TABS g_OptionsTabs[] = {
|
|||||||
{ _T("Snip"), NULL }
|
{ _T("Snip"), NULL }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static const TCHAR* g_RecordingFormats[] = {
|
||||||
|
_T("GIF"),
|
||||||
|
_T("MP4")
|
||||||
|
};
|
||||||
|
|
||||||
float g_ZoomLevels[] = {
|
float g_ZoomLevels[] = {
|
||||||
1.25,
|
1.25,
|
||||||
1.50,
|
1.50,
|
||||||
@@ -99,6 +107,8 @@ float g_ZoomLevels[] = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
DWORD g_FramerateOptions[] = {
|
DWORD g_FramerateOptions[] = {
|
||||||
|
15,
|
||||||
|
24,
|
||||||
30,
|
30,
|
||||||
60
|
60
|
||||||
};
|
};
|
||||||
@@ -152,12 +162,15 @@ BOOLEAN g_running = TRUE;
|
|||||||
|
|
||||||
// Screen recording globals
|
// Screen recording globals
|
||||||
#define DEFAULT_RECORDING_FILE L"Recording.mp4"
|
#define DEFAULT_RECORDING_FILE L"Recording.mp4"
|
||||||
|
#define DEFAULT_GIF_RECORDING_FILE L"Recording.gif"
|
||||||
|
|
||||||
BOOL g_RecordToggle = FALSE;
|
BOOL g_RecordToggle = FALSE;
|
||||||
BOOL g_RecordCropping = FALSE;
|
BOOL g_RecordCropping = FALSE;
|
||||||
SelectRectangle g_SelectRectangle;
|
SelectRectangle g_SelectRectangle;
|
||||||
std::wstring g_RecordingSaveLocation;
|
std::wstring g_RecordingSaveLocation;
|
||||||
winrt::IDirect3DDevice g_RecordDevice{ nullptr };
|
winrt::IDirect3DDevice g_RecordDevice{ nullptr };
|
||||||
std::shared_ptr<VideoRecordingSession> g_RecordingSession = nullptr;
|
std::shared_ptr<VideoRecordingSession> g_RecordingSession = nullptr;
|
||||||
|
std::shared_ptr<GifRecordingSession> g_GifRecordingSession = nullptr;
|
||||||
|
|
||||||
type_pGetMonitorInfo pGetMonitorInfo;
|
type_pGetMonitorInfo pGetMonitorInfo;
|
||||||
type_MonitorFromPoint pMonitorFromPoint;
|
type_MonitorFromPoint pMonitorFromPoint;
|
||||||
@@ -1771,6 +1784,58 @@ INT_PTR CALLBACK OptionsTabProc( HWND hDlg, UINT message,
|
|||||||
case WM_INITDIALOG:
|
case WM_INITDIALOG:
|
||||||
return TRUE;
|
return TRUE;
|
||||||
case WM_COMMAND:
|
case WM_COMMAND:
|
||||||
|
// Handle combo box selection changes
|
||||||
|
if (HIWORD(wParam) == CBN_SELCHANGE) {
|
||||||
|
if (LOWORD(wParam) == IDC_RECORD_SCALING) {
|
||||||
|
|
||||||
|
int format = static_cast<int>(SendMessage(GetDlgItem(hDlg, IDC_RECORD_FORMAT), CB_GETCURSEL, 0, 0));
|
||||||
|
int scale = static_cast<int>(SendMessage(GetDlgItem(hDlg, IDC_RECORD_SCALING), CB_GETCURSEL, 0, 0));
|
||||||
|
if(format == 0)
|
||||||
|
{
|
||||||
|
g_RecordScalingGIF = static_cast<BYTE>((scale + 1) * 10);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
g_RecordScalingMP4 = static_cast<BYTE>((scale + 1) * 10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (LOWORD(wParam) == IDC_RECORD_FORMAT) {
|
||||||
|
// Get the currently selected format
|
||||||
|
int selection = static_cast<int>(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<LPARAM>(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<int>(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 )) {
|
switch ( LOWORD( wParam )) {
|
||||||
case IDC_ADVANCED_BREAK:
|
case IDC_ADVANCED_BREAK:
|
||||||
DialogBox( g_hInstance, L"ADVANCED_BREAK", hDlg, AdvancedBreakProc );
|
DialogBox( g_hInstance, L"ADVANCED_BREAK", hDlg, AdvancedBreakProc );
|
||||||
@@ -1914,6 +1979,8 @@ void UnregisterAllHotkeys( HWND hWnd )
|
|||||||
UnregisterHotKey( hWnd, SNIP_SAVE_HOTKEY);
|
UnregisterHotKey( hWnd, SNIP_SAVE_HOTKEY);
|
||||||
UnregisterHotKey( hWnd, DEMOTYPE_HOTKEY );
|
UnregisterHotKey( hWnd, DEMOTYPE_HOTKEY );
|
||||||
UnregisterHotKey( hWnd, DEMOTYPE_RESET_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_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);
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -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<WPARAM>(i), static_cast<LPARAM>(0));
|
SendMessage(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_RECORD_FRAME_RATE), CB_SETCURSEL, static_cast<WPARAM>(i), static_cast<LPARAM>(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<UINT>(CB_ADDSTRING), static_cast<WPARAM>(0), reinterpret_cast<LPARAM>(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<WPARAM>(selection), static_cast<LPARAM>(0) );
|
||||||
|
|
||||||
for(unsigned int i = 1; i < 11; i++) {
|
for(unsigned int i = 1; i < 11; i++) {
|
||||||
|
|
||||||
_stprintf(text, L"%2.1f", (static_cast<double>(i)) / 10 );
|
_stprintf(text, L"%2.1f", (static_cast<double>(i)) / 10 );
|
||||||
SendMessage(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_RECORD_SCALING), static_cast<UINT>(CB_ADDSTRING),
|
SendMessage(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_RECORD_SCALING), static_cast<UINT>(CB_ADDSTRING),
|
||||||
static_cast<WPARAM>(0), reinterpret_cast<LPARAM>(text));
|
static_cast<WPARAM>(0), reinterpret_cast<LPARAM>(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<WPARAM>(i)-1, static_cast<LPARAM>(0));
|
||||||
|
}
|
||||||
|
if (g_RecordingFormat == RecordingFormat::MP4 && i*10 == g_RecordScalingMP4 ) {
|
||||||
|
|
||||||
SendMessage(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_RECORD_SCALING), CB_SETCURSEL, static_cast<WPARAM>(i)-1, static_cast<LPARAM>(0));
|
SendMessage(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_RECORD_SCALING), CB_SETCURSEL, static_cast<WPARAM>(i)-1, static_cast<LPARAM>(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
|
// Add the microphone devices to the combo box and set the current selection
|
||||||
SendMessage( GetDlgItem( g_OptionsTabs[RECORD_PAGE].hPage, IDC_MICROPHONE ), static_cast<UINT>(CB_ADDSTRING), static_cast<WPARAM>(0), reinterpret_cast<LPARAM>(L"Default"));
|
SendMessage( GetDlgItem( g_OptionsTabs[RECORD_PAGE].hPage, IDC_MICROPHONE ), static_cast<UINT>(CB_ADDSTRING), static_cast<WPARAM>(0), reinterpret_cast<LPARAM>(L"Default"));
|
||||||
size_t selection = 0;
|
selection = 0;
|
||||||
for( size_t i = 0; i < microphones.size(); i++ )
|
for( size_t i = 0; i < microphones.size(); i++ )
|
||||||
{
|
{
|
||||||
SendMessage( GetDlgItem( g_OptionsTabs[RECORD_PAGE].hPage, IDC_MICROPHONE ), static_cast<UINT>(CB_ADDSTRING), static_cast<WPARAM>(0), reinterpret_cast<LPARAM>(microphones[i].second.c_str()) );
|
SendMessage( GetDlgItem( g_OptionsTabs[RECORD_PAGE].hPage, IDC_MICROPHONE ), static_cast<UINT>(CB_ADDSTRING), static_cast<WPARAM>(0), reinterpret_cast<LPARAM>(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<WPARAM>(selection), static_cast<LPARAM>(0) );
|
SendMessage( GetDlgItem( g_OptionsTabs[RECORD_PAGE].hPage, IDC_MICROPHONE ), CB_SETCURSEL, static_cast<WPARAM>(selection), static_cast<LPARAM>(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 )
|
if( GetFileAttributes( g_DemoTypeFile ) == -1 )
|
||||||
{
|
{
|
||||||
memset( g_DemoTypeFile, 0, sizeof( g_DemoTypeFile ) );
|
memset( g_DemoTypeFile, 0, sizeof( g_DemoTypeFile ) );
|
||||||
@@ -2249,7 +2345,17 @@ INT_PTR CALLBACK OptionsProc( HWND hDlg, UINT message,
|
|||||||
text[2] = 0;
|
text[2] = 0;
|
||||||
newTimeout = _tstoi( text );
|
newTimeout = _tstoi( text );
|
||||||
|
|
||||||
g_RecordFrameRate = g_FramerateOptions[SendMessage(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_RECORD_FRAME_RATE), static_cast<UINT>(CB_GETCURSEL), static_cast<WPARAM>(0), static_cast<LPARAM>(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<UINT>(CB_GETCURSEL), static_cast<WPARAM>(0), static_cast<LPARAM>(0))];
|
||||||
|
}
|
||||||
|
|
||||||
|
g_RecordingFormat = static_cast<RecordingFormat>(SendMessage(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_RECORD_FORMAT), static_cast<UINT>(CB_GETCURSEL), static_cast<WPARAM>(0), static_cast<LPARAM>(0)));
|
||||||
g_RecordScaling = static_cast<int>(SendMessage(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_RECORD_SCALING), static_cast<UINT>(CB_GETCURSEL), static_cast<WPARAM>(0), static_cast<LPARAM>(0)) * 10 + 10);
|
g_RecordScaling = static_cast<int>(SendMessage(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_RECORD_SCALING), static_cast<UINT>(CB_GETCURSEL), static_cast<WPARAM>(0), static_cast<LPARAM>(0)) * 10 + 10);
|
||||||
|
|
||||||
// Get the selected microphone
|
// Get the selected microphone
|
||||||
@@ -3395,6 +3501,12 @@ void StopRecording()
|
|||||||
g_RecordingSession = nullptr;
|
g_RecordingSession = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ( g_GifRecordingSession != nullptr ) {
|
||||||
|
|
||||||
|
g_GifRecordingSession->Close();
|
||||||
|
g_GifRecordingSession = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
g_RecordToggle = FALSE;
|
g_RecordToggle = FALSE;
|
||||||
#if WINDOWS_CURSOR_RECORDING_WORKAROUND
|
#if WINDOWS_CURSOR_RECORDING_WORKAROUND
|
||||||
|
|
||||||
@@ -3451,7 +3563,10 @@ winrt::fire_and_forget StartRecordingAsync( HWND hWnd, LPRECT rcCrop, HWND hWndR
|
|||||||
auto tempFolderPath = std::filesystem::temp_directory_path().wstring();
|
auto tempFolderPath = std::filesystem::temp_directory_path().wstring();
|
||||||
auto tempFolder = co_await winrt::StorageFolder::GetFolderFromPathAsync( tempFolderPath );
|
auto tempFolder = co_await winrt::StorageFolder::GetFolderFromPathAsync( tempFolderPath );
|
||||||
auto appFolder = co_await tempFolder.CreateFolderAsync( L"ZoomIt", winrt::CreationCollisionOption::OpenIfExists );
|
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
|
// Get the device
|
||||||
auto d3dDevice = util::CreateD3D11Device();
|
auto d3dDevice = util::CreateD3D11Device();
|
||||||
@@ -3474,21 +3589,40 @@ winrt::fire_and_forget StartRecordingAsync( HWND hWnd, LPRECT rcCrop, HWND hWndR
|
|||||||
item = util::CreateCaptureItemForMonitor( hMon );
|
item = util::CreateCaptureItemForMonitor( hMon );
|
||||||
|
|
||||||
auto stream = co_await file.OpenAsync( winrt::FileAccessMode::ReadWrite );
|
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 )
|
// Create the appropriate recording session based on format
|
||||||
g_RecordingSession->EnableCursorCapture( false );
|
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
|
co_await g_GifRecordingSession->StartAsync();
|
||||||
if( g_RecordingSession == nullptr ) {
|
}
|
||||||
|
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;
|
g_bSaveInProgress = true;
|
||||||
|
|
||||||
@@ -3504,11 +3638,24 @@ winrt::fire_and_forget StartRecordingAsync( HWND hWnd, LPRECT rcCrop, HWND hWndR
|
|||||||
wil::com_ptr<IShellItem> videosItem;
|
wil::com_ptr<IShellItem> videosItem;
|
||||||
if( SUCCEEDED ( SHGetKnownFolderItem( FOLDERID_Videos, KF_FLAG_DEFAULT, nullptr, IID_IShellItem, (void**) videosItem.put() ) ) )
|
if( SUCCEEDED ( SHGetKnownFolderItem( FOLDERID_Videos, KF_FLAG_DEFAULT, nullptr, IID_IShellItem, (void**) videosItem.put() ) ) )
|
||||||
saveDialog->SetDefaultFolder( videosItem.get() );
|
saveDialog->SetDefaultFolder( videosItem.get() );
|
||||||
saveDialog->SetDefaultExtension( L".mp4" );
|
|
||||||
COMDLG_FILTERSPEC fileTypes[] = {
|
// Set file type based on the recording format
|
||||||
{ L"MP4 Video", L"*.mp4" }
|
if (g_RecordingFormat == RecordingFormat::GIF)
|
||||||
};
|
{
|
||||||
saveDialog->SetFileTypes( _countof( fileTypes ), fileTypes );
|
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) {
|
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;
|
wil::unique_cotaskmem_string folderPath;
|
||||||
if (SUCCEEDED(saveDialog->GetFolder(shellItem.put())) && SUCCEEDED(shellItem->GetDisplayName(SIGDN_FILESYSPATH, folderPath.put())))
|
if (SUCCEEDED(saveDialog->GetFolder(shellItem.put())) && SUCCEEDED(shellItem->GetDisplayName(SIGDN_FILESYSPATH, folderPath.put())))
|
||||||
g_RecordingSaveLocation = folderPath.get();
|
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();
|
auto suggestedName = GetUniqueRecordingFilename();
|
||||||
saveDialog->SetFileName( suggestedName.c_str() );
|
saveDialog->SetFileName( suggestedName.c_str() );
|
||||||
|
|
||||||
@@ -3566,6 +3717,7 @@ winrt::fire_and_forget StartRecordingAsync( HWND hWnd, LPRECT rcCrop, HWND hWndR
|
|||||||
}
|
}
|
||||||
co_await file.DeleteAsync();
|
co_await file.DeleteAsync();
|
||||||
g_RecordingSession = nullptr;
|
g_RecordingSession = nullptr;
|
||||||
|
g_GifRecordingSession = nullptr;
|
||||||
}
|
}
|
||||||
} catch( const winrt::hresult_error& error ) {
|
} catch( const winrt::hresult_error& error ) {
|
||||||
|
|
||||||
@@ -3792,6 +3944,13 @@ LRESULT APIENTRY MainWndProc(
|
|||||||
|
|
||||||
reg.ReadRegSettings( RegSettings );
|
reg.ReadRegSettings( RegSettings );
|
||||||
|
|
||||||
|
// 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
|
// to support migrating from
|
||||||
if ((g_PenColor >> 24) == 0) {
|
if ((g_PenColor >> 24) == 0) {
|
||||||
g_PenColor |= 0xFF << 24;
|
g_PenColor |= 0xFF << 24;
|
||||||
@@ -4206,6 +4365,8 @@ LRESULT APIENTRY MainWndProc(
|
|||||||
case RECORD_HOTKEY:
|
case RECORD_HOTKEY:
|
||||||
case RECORD_CROP_HOTKEY:
|
case RECORD_CROP_HOTKEY:
|
||||||
case RECORD_WINDOW_HOTKEY:
|
case RECORD_WINDOW_HOTKEY:
|
||||||
|
case RECORD_GIF_HOTKEY:
|
||||||
|
case RECORD_GIF_WINDOW_HOTKEY:
|
||||||
|
|
||||||
//
|
//
|
||||||
// Recording
|
// Recording
|
||||||
@@ -4335,7 +4496,7 @@ LRESULT APIENTRY MainWndProc(
|
|||||||
cropRc = {};
|
cropRc = {};
|
||||||
|
|
||||||
// if we're recording a window, get the window
|
// if we're recording a window, get the window
|
||||||
if (wParam == RECORD_WINDOW_HOTKEY)
|
if (wParam == RECORD_WINDOW_HOTKEY || wParam == RECORD_GIF_WINDOW_HOTKEY)
|
||||||
{
|
{
|
||||||
GetCursorPos(&cursorPos);
|
GetCursorPos(&cursorPos);
|
||||||
hWndRecord = WindowFromPoint(cursorPos);
|
hWndRecord = WindowFromPoint(cursorPos);
|
||||||
@@ -4353,6 +4514,7 @@ LRESULT APIENTRY MainWndProc(
|
|||||||
if( g_RecordToggle == FALSE )
|
if( g_RecordToggle == FALSE )
|
||||||
{
|
{
|
||||||
g_RecordToggle = TRUE;
|
g_RecordToggle = TRUE;
|
||||||
|
|
||||||
#ifdef __ZOOMIT_POWERTOYS__
|
#ifdef __ZOOMIT_POWERTOYS__
|
||||||
if( g_StartedByPowerToys )
|
if( g_StartedByPowerToys )
|
||||||
{
|
{
|
||||||
@@ -6147,6 +6309,13 @@ LRESULT APIENTRY MainWndProc(
|
|||||||
showOptions = TRUE;
|
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)
|
if (showOptions)
|
||||||
{
|
{
|
||||||
// To open the PowerToys settings in the ZoomIt page.
|
// To open the PowerToys settings in the ZoomIt page.
|
||||||
|
|||||||
@@ -93,6 +93,7 @@
|
|||||||
#define IDC_DEMOTYPE_SLIDER2 1074
|
#define IDC_DEMOTYPE_SLIDER2 1074
|
||||||
#define IDC_DEMOTYPE_STATIC2 1074
|
#define IDC_DEMOTYPE_STATIC2 1074
|
||||||
#define IDC_COPYRIGHT 1075
|
#define IDC_COPYRIGHT 1075
|
||||||
|
#define IDC_RECORD_FORMAT 1076
|
||||||
#define IDC_PEN_WIDTH 1105
|
#define IDC_PEN_WIDTH 1105
|
||||||
#define IDC_TIMER 1106
|
#define IDC_TIMER 1106
|
||||||
#define IDC_SMOOTH_IMAGE 1107
|
#define IDC_SMOOTH_IMAGE 1107
|
||||||
|
|||||||
@@ -14,6 +14,9 @@ namespace winrt::PowerToys::ZoomItSettingsInterop::implementation
|
|||||||
const unsigned int SPECIAL_SEMANTICS_SHORTCUT = 1;
|
const unsigned int SPECIAL_SEMANTICS_SHORTCUT = 1;
|
||||||
const unsigned int SPECIAL_SEMANTICS_COLOR = 2;
|
const unsigned int SPECIAL_SEMANTICS_COLOR = 2;
|
||||||
const unsigned int SPECIAL_SEMANTICS_LOG_FONT = 3;
|
const unsigned int SPECIAL_SEMANTICS_LOG_FONT = 3;
|
||||||
|
const unsigned int SPECIAL_SEMANTICS_RECORDING_FORMAT = 4;
|
||||||
|
const unsigned int SPECIAL_SEMANTICS_RECORD_SCALING_GIF = 5;
|
||||||
|
const unsigned int SPECIAL_SEMANTICS_RECORD_SCALING_MP4 = 6;
|
||||||
|
|
||||||
std::vector<unsigned char> base64_decode(const std::wstring& base64_string)
|
std::vector<unsigned char> base64_decode(const std::wstring& base64_string)
|
||||||
{
|
{
|
||||||
@@ -72,6 +75,9 @@ namespace winrt::PowerToys::ZoomItSettingsInterop::implementation
|
|||||||
{ L"PenColor", SPECIAL_SEMANTICS_COLOR },
|
{ L"PenColor", SPECIAL_SEMANTICS_COLOR },
|
||||||
{ L"BreakPenColor", SPECIAL_SEMANTICS_COLOR },
|
{ L"BreakPenColor", SPECIAL_SEMANTICS_COLOR },
|
||||||
{ L"Font", SPECIAL_SEMANTICS_LOG_FONT },
|
{ 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()
|
hstring ZoomItSettings::LoadSettingsJson()
|
||||||
@@ -103,6 +109,11 @@ namespace winrt::PowerToys::ZoomItSettingsInterop::implementation
|
|||||||
value & 0xFF);
|
value & 0xFF);
|
||||||
_settings.add_property(curSetting->ValueName, hotkey.get_json());
|
_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)
|
else if (special_semantics->second == SPECIAL_SEMANTICS_COLOR)
|
||||||
{
|
{
|
||||||
/* PowerToys settings likes colors as #FFFFFF strings.
|
/* PowerToys settings likes colors as #FFFFFF strings.
|
||||||
@@ -156,6 +167,9 @@ namespace winrt::PowerToys::ZoomItSettingsInterop::implementation
|
|||||||
curSetting++;
|
curSetting++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DWORD recordScaling = (g_RecordingFormat == static_cast<RecordingFormat>(0)) ? g_RecordScalingGIF : g_RecordScalingMP4;
|
||||||
|
_settings.add_property<DWORD>(L"RecordScaling", recordScaling);
|
||||||
|
|
||||||
return _settings.get_raw_json().Stringify();
|
return _settings.get_raw_json().Stringify();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,6 +181,8 @@ namespace winrt::PowerToys::ZoomItSettingsInterop::implementation
|
|||||||
PowerToysSettings::PowerToyValues valuesFromSettings =
|
PowerToysSettings::PowerToyValues valuesFromSettings =
|
||||||
PowerToysSettings::PowerToyValues::from_json_string(json, L"ZoomIt");
|
PowerToysSettings::PowerToyValues::from_json_string(json, L"ZoomIt");
|
||||||
|
|
||||||
|
bool formatChanged = false;
|
||||||
|
|
||||||
PREG_SETTING curSetting = RegSettings;
|
PREG_SETTING curSetting = RegSettings;
|
||||||
while (curSetting->ValueName)
|
while (curSetting->ValueName)
|
||||||
{
|
{
|
||||||
@@ -212,6 +228,42 @@ namespace winrt::PowerToys::ZoomItSettingsInterop::implementation
|
|||||||
*static_cast<PDWORD>(curSetting->Setting) = value;
|
*static_cast<PDWORD>(curSetting->Setting) = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (special_semantics->second == SPECIAL_SEMANTICS_RECORDING_FORMAT)
|
||||||
|
{
|
||||||
|
// Convert string ("GIF" or "MP4") to DWORD enum value (0=GIF, 1=MP4)
|
||||||
|
auto possibleValue = valuesFromSettings.get_string_value(L"RecordFormat");
|
||||||
|
if (possibleValue.has_value())
|
||||||
|
{
|
||||||
|
RecordingFormat oldFormat = g_RecordingFormat;
|
||||||
|
DWORD formatValue = (possibleValue.value() == L"GIF") ? 0 : 1;
|
||||||
|
RecordingFormat newFormat = static_cast<RecordingFormat>(formatValue);
|
||||||
|
|
||||||
|
*static_cast<PDWORD>(curSetting->Setting) = formatValue;
|
||||||
|
|
||||||
|
if (oldFormat != newFormat)
|
||||||
|
{
|
||||||
|
formatChanged = true;
|
||||||
|
|
||||||
|
if (oldFormat == static_cast<RecordingFormat>(0))
|
||||||
|
{
|
||||||
|
g_RecordScalingGIF = g_RecordScaling;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
g_RecordScalingMP4 = g_RecordScaling;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newFormat == static_cast<RecordingFormat>(0))
|
||||||
|
{
|
||||||
|
g_RecordScaling = g_RecordScalingGIF;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
g_RecordScaling = g_RecordScalingMP4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
else if (special_semantics->second == SPECIAL_SEMANTICS_COLOR)
|
else if (special_semantics->second == SPECIAL_SEMANTICS_COLOR)
|
||||||
{
|
{
|
||||||
/* PowerToys settings likes colors as #FFFFFF strings.
|
/* PowerToys settings likes colors as #FFFFFF strings.
|
||||||
@@ -275,6 +327,22 @@ namespace winrt::PowerToys::ZoomItSettingsInterop::implementation
|
|||||||
}
|
}
|
||||||
curSetting++;
|
curSetting++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto recordScalingValue = valuesFromSettings.get_uint_value(L"RecordScaling");
|
||||||
|
if (recordScalingValue.has_value() && !formatChanged)
|
||||||
|
{
|
||||||
|
g_RecordScaling = recordScalingValue.value();
|
||||||
|
|
||||||
|
if (g_RecordingFormat == static_cast<RecordingFormat>(0))
|
||||||
|
{
|
||||||
|
g_RecordScalingGIF = recordScalingValue.value();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
g_RecordScalingMP4 = recordScalingValue.value();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
reg.WriteRegSettings(RegSettings);
|
reg.WriteRegSettings(RegSettings);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -87,6 +87,8 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
|||||||
|
|
||||||
public IntProperty RecordScaling { get; set; }
|
public IntProperty RecordScaling { get; set; }
|
||||||
|
|
||||||
|
public StringProperty RecordFormat { get; set; }
|
||||||
|
|
||||||
public BoolProperty CaptureAudio { get; set; }
|
public BoolProperty CaptureAudio { get; set; }
|
||||||
|
|
||||||
public StringProperty MicrophoneDeviceId { get; set; }
|
public StringProperty MicrophoneDeviceId { get; set; }
|
||||||
|
|||||||
@@ -241,6 +241,12 @@
|
|||||||
<ComboBoxItem>1.0</ComboBoxItem>
|
<ComboBoxItem>1.0</ComboBoxItem>
|
||||||
</ComboBox>
|
</ComboBox>
|
||||||
</tkcontrols:SettingsCard>
|
</tkcontrols:SettingsCard>
|
||||||
|
<tkcontrols:SettingsCard Name="ZoomItRecordFormat" x:Uid="ZoomIt_Record_Format">
|
||||||
|
<ComboBox MinWidth="{StaticResource SettingActionControlMinWidth}" SelectedIndex="{x:Bind Path=ViewModel.RecordFormatIndex, Mode=TwoWay}">
|
||||||
|
<ComboBoxItem>GIF</ComboBoxItem>
|
||||||
|
<ComboBoxItem>MP4</ComboBoxItem>
|
||||||
|
</ComboBox>
|
||||||
|
</tkcontrols:SettingsCard>
|
||||||
<tkcontrols:SettingsCard Name="ZoomItRecordCaptureAudio" x:Uid="ZoomIt_Record_CaptureAudio">
|
<tkcontrols:SettingsCard Name="ZoomItRecordCaptureAudio" x:Uid="ZoomIt_Record_CaptureAudio">
|
||||||
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.RecordCaptureAudio, Mode=TwoWay}" />
|
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.RecordCaptureAudio, Mode=TwoWay}" />
|
||||||
</tkcontrols:SettingsCard>
|
</tkcontrols:SettingsCard>
|
||||||
|
|||||||
@@ -5046,6 +5046,9 @@ To record a specific window, enter the hotkey with the Alt key in the opposite m
|
|||||||
<data name="ZoomIt_Record_Scaling.Header" xml:space="preserve">
|
<data name="ZoomIt_Record_Scaling.Header" xml:space="preserve">
|
||||||
<value>Scaling</value>
|
<value>Scaling</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="ZoomIt_Record_Format.Header" xml:space="preserve">
|
||||||
|
<value>Format</value>
|
||||||
|
</data>
|
||||||
<data name="ZoomIt_Record_CaptureAudio.Header" xml:space="preserve">
|
<data name="ZoomIt_Record_CaptureAudio.Header" xml:space="preserve">
|
||||||
<value>Capture audio input</value>
|
<value>Capture audio input</value>
|
||||||
</data>
|
</data>
|
||||||
|
|||||||
@@ -652,6 +652,54 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int RecordFormatIndex
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_zoomItSettings.Properties.RecordFormat.Value == "GIF")
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_zoomItSettings.Properties.RecordFormat.Value == "MP4")
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
set
|
||||||
|
{
|
||||||
|
int format = 0;
|
||||||
|
if (_zoomItSettings.Properties.RecordFormat.Value == "GIF")
|
||||||
|
{
|
||||||
|
format = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_zoomItSettings.Properties.RecordFormat.Value == "MP4")
|
||||||
|
{
|
||||||
|
format = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (format != value)
|
||||||
|
{
|
||||||
|
_zoomItSettings.Properties.RecordFormat.Value = value == 0 ? "GIF" : "MP4";
|
||||||
|
OnPropertyChanged(nameof(RecordFormatIndex));
|
||||||
|
NotifySettingsChanged();
|
||||||
|
|
||||||
|
// Reload settings to get the new format's scaling value
|
||||||
|
var reloadedSettings = global::PowerToys.ZoomItSettingsInterop.ZoomItSettings.LoadSettingsJson();
|
||||||
|
var reloaded = JsonSerializer.Deserialize<ZoomItSettings>(reloadedSettings, _serializerOptions);
|
||||||
|
if (reloaded != null && reloaded.Properties != null)
|
||||||
|
{
|
||||||
|
_zoomItSettings.Properties.RecordScaling.Value = reloaded.Properties.RecordScaling.Value;
|
||||||
|
OnPropertyChanged(nameof(RecordScalingIndex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public bool RecordCaptureAudio
|
public bool RecordCaptureAudio
|
||||||
{
|
{
|
||||||
get => _zoomItSettings.Properties.CaptureAudio.Value;
|
get => _zoomItSettings.Properties.CaptureAudio.Value;
|
||||||
|
|||||||
Reference in New Issue
Block a user