diff --git a/SpoutCamDX.vcxproj b/SpoutCamDX.vcxproj
index 394c5e2..5b3c6b2 100644
--- a/SpoutCamDX.vcxproj
+++ b/SpoutCamDX.vcxproj
@@ -28,23 +28,23 @@
DynamicLibrary
- MultiByte
+ Unicode
v141
DynamicLibrary
- MultiByte
+ Unicode
v141
DynamicLibrary
v141
- MultiByte
+ Unicode
DynamicLibrary
v141
- MultiByte
+ Unicode
@@ -141,6 +141,8 @@
copy /y $(TargetPath) release\win32\SpoutCam32.ax
+
+
@@ -167,6 +169,9 @@
copy /y $(TargetPath) release\x64\SpoutCam64.ax
+
+ PerMonitorHighDPIAware
+
diff --git a/SpoutDX/winproj2017/SpoutDX.vcxproj b/SpoutDX/winproj2017/SpoutDX.vcxproj
index b70aca5..2c25244 100644
--- a/SpoutDX/winproj2017/SpoutDX.vcxproj
+++ b/SpoutDX/winproj2017/SpoutDX.vcxproj
@@ -50,27 +50,27 @@
StaticLibrary
true
- MultiByte
+ Unicode
v141
StaticLibrary
true
- MultiByte
+ Unicode
v141
StaticLibrary
false
true
- MultiByte
+ Unicode
v141
StaticLibrary
false
true
- MultiByte
+ Unicode
v141
diff --git a/baseclasses/winproj2017/BaseClasses.vcxproj b/baseclasses/winproj2017/BaseClasses.vcxproj
index 3a67e78..bf413f6 100644
--- a/baseclasses/winproj2017/BaseClasses.vcxproj
+++ b/baseclasses/winproj2017/BaseClasses.vcxproj
@@ -43,6 +43,7 @@
StaticLibrary
v141
+ Unicode
diff --git a/release/win32/SpoutCam32.ax b/release/win32/SpoutCam32.ax
index 8399934..2109a5f 100644
Binary files a/release/win32/SpoutCam32.ax and b/release/win32/SpoutCam32.ax differ
diff --git a/release/win32/SpoutCamProperties.cmd b/release/win32/SpoutCamProperties.cmd
new file mode 100644
index 0000000..7e6ca20
--- /dev/null
+++ b/release/win32/SpoutCamProperties.cmd
@@ -0,0 +1,3 @@
+@echo off
+cd /d "%~dp0"
+START C:\Windows\System32\rundll32.exe SpoutCam32.ax,Configure
diff --git a/release/win32/SpoutCamSettings.cmd b/release/win32/SpoutCamSettings.cmd
deleted file mode 100644
index 75eff6b..0000000
--- a/release/win32/SpoutCamSettings.cmd
+++ /dev/null
@@ -1,3 +0,0 @@
-@echo off
-cd /d "%~dp0"
-C:\Windows\System32\rundll32.exe SpoutCam32.ax,Configure
diff --git a/release/x64/SpoutCam64.ax b/release/x64/SpoutCam64.ax
index cfc2ca8..bcc7932 100644
Binary files a/release/x64/SpoutCam64.ax and b/release/x64/SpoutCam64.ax differ
diff --git a/release/x64/SpoutCamProperties.cmd b/release/x64/SpoutCamProperties.cmd
new file mode 100644
index 0000000..e2b88f7
--- /dev/null
+++ b/release/x64/SpoutCamProperties.cmd
@@ -0,0 +1,3 @@
+@echo off
+cd /d "%~dp0"
+START C:\Windows\System32\rundll32.exe SpoutCam64.ax,Configure
diff --git a/release/x64/SpoutCamSettings.cmd b/release/x64/SpoutCamSettings.cmd
deleted file mode 100644
index a6aaf16..0000000
--- a/release/x64/SpoutCamSettings.cmd
+++ /dev/null
@@ -1,3 +0,0 @@
-@echo off
-cd /d "%~dp0"
-C:\Windows\System32\rundll32.exe SpoutCam64.ax,Configure
diff --git a/source/cam.aps b/source/cam.aps
new file mode 100644
index 0000000..a67f5b9
Binary files /dev/null and b/source/cam.aps differ
diff --git a/source/cam.cpp b/source/cam.cpp
index d74feee..b91f1ed 100644
--- a/source/cam.cpp
+++ b/source/cam.cpp
@@ -241,25 +241,46 @@
Add options for image mirror, flip and swap (RGB <> BGR)
Requires SpoutCamSettings - Version 2.005 or greater
Verson 2.017
+ 09.10.20 Valentin Schmidt: Various changes to add a settings dialog
+ Verson 2.018
+ 09.10.20 Valentin Schmidt: added Mirror/Flip/Swap options to dialog
+ Verson 2.019
+ 12.10.20 CVCam::CreateInstance - SetProcessDPIAware() for clear options dialog
+ put_Settings simplify code for flags
+ Introduce ReleaseCamReceiver()
+ Remove connect/disconnect test
+ Showstatic if user and filter resolutions are different
+ Update verison.h - Version 2.020
+ Correct CSpoutCamProperties::OnApplyChanges()
+ for handle to fps and resolution controls for old comparison
+ 13.10.20 CSpoutCamProperties::OnApplyChanges() to restore
+ fps & resolution list items on response to no change
+ Correct pvscc->InputSize.cx/cy in GetStreamCaps
+ Clean up unused code
+ Version 2.021
+ 13.10.20 Correct Flip true by default
+ Made seed for xorshiftRand static to prevent frozen image
+ Returned to timing by RED5
+ Conditional receive if DirectX initialization failed
+ Version 2.022
+ 16.10.20 FillBuffer correct cast for TimeGetTime calculations
+ Use TimeBeginPeriod for maximum precision of TimeGetTime and Sleep
+ DirectX device not required - in SpoutDX class.
+ Do not go to static on first DirectX initialize.
+ 17.10.20 Change to Unicode for WCHAR folder name support
+ Verson 2.023
*/
#pragma warning(disable:4244)
#pragma warning(disable:4711)
-#include
-#include
-#include
-#include
-
#include "cam.h"
-static HWND hwndButton = NULL; // dummy window for opengl context
-
// This is just a fast rand so that the static image isn't too slow for higher resolutions
// Based on Marsaglia's xorshift generator (http://www.jstatsoft.org/v08/i14/paper)
// and copied from (https://excamera.com/sphinx/article-xorshift.html)
-uint32_t seed = 7; // 100% random seed value
+static uint32_t seed = 7; // 100% random seed value
static uint32_t xorshiftRand()
{
seed ^= seed << 13;
@@ -268,7 +289,6 @@ static uint32_t xorshiftRand()
return seed;
}
-
//////////////////////////////////////////////////////////////////////////
// CVCam is the source filter which masquerades as a capture device
//////////////////////////////////////////////////////////////////////////
@@ -279,7 +299,10 @@ CUnknown * WINAPI CVCam::CreateInstance(LPUNKNOWN lpunk, HRESULT *phr)
// debug console window
// OpenSpoutConsole(); // Empty console
// EnableSpoutLog(); // Show error logs
- // printf("SpoutCamDX ~~ Vers 2.017\n");
+ // printf("SpoutCamDX ~~ Vers 2.023\n");
+
+ // For clear options dialog for scaled display
+ SetProcessDPIAware();
CUnknown *punk = new CVCam(lpunk, phr);
@@ -287,7 +310,7 @@ CUnknown * WINAPI CVCam::CreateInstance(LPUNKNOWN lpunk, HRESULT *phr)
}
CVCam::CVCam(LPUNKNOWN lpunk, HRESULT *phr) :
- CSource(NAME(SpoutCamName), lpunk, CLSID_SpoutCam)
+ CSource(NAME(SPOUTCAMNAME), lpunk, CLSID_SpoutCam) //VS: replaced SpoutCamName with makro SPOUTCAMNAME, NAME() expects LPCTSTR
{
ASSERT(phr);
@@ -297,6 +320,7 @@ CVCam::CVCam(LPUNKNOWN lpunk, HRESULT *phr) :
m_paStreams = (CSourceStream **)new CVCamStream*[1];
m_paStreams[0] = new CVCamStream(phr, this, (LPCWSTR)&SpoutCamName);
+ if (phr) *phr = S_OK; //VS
}
// Retrieves pointers to the supported interfaces on an object.
@@ -308,6 +332,12 @@ HRESULT CVCam::QueryInterface(REFIID riid, void **ppv)
riid == _uuidof(IAMDroppedFrames) ||
riid == _uuidof(IKsPropertySet))
return m_paStreams[0]->QueryInterface(riid, ppv);
+ //<==================== VS-START ====================>
+ else if (riid == IID_ICamSettings)
+ return GetInterface((ICamSettings *)this, ppv);
+ else if (riid == IID_ISpecifyPropertyPages)
+ return GetInterface((ISpecifyPropertyPages *)this, ppv);
+ //<==================== VS-END ======================>
else
return CSource::QueryInterface(riid, ppv);
}
@@ -332,6 +362,33 @@ STDMETHODIMP CVCam::JoinFilterGraph(
return hr;
}
+//<==================== VS-START ====================>
+
+//////////////////////////////////////////////////////////////////////////
+// ISpecifyPropertyPages interface
+//////////////////////////////////////////////////////////////////////////
+
+// GetPages
+// Returns the clsid's of the property pages we support
+STDMETHODIMP CVCam::GetPages(CAUUID *pPages)
+{
+ CheckPointer(pPages, E_POINTER);
+ pPages->cElems = 1;
+ pPages->pElems = (GUID *)CoTaskMemAlloc(sizeof(GUID));
+ if (pPages->pElems == NULL)
+ return E_OUTOFMEMORY;
+ pPages->pElems[0] = CLSID_SpoutCamPropertyPage;
+ return S_OK;
+}
+
+STDMETHODIMP CVCam::put_Settings(DWORD dwFps, DWORD dwResolution, DWORD dwMirror, DWORD dwSwap, DWORD dwFlip)
+{
+ ((CVCamStream *)m_paStreams[0])->put_Settings(dwFps, dwResolution, dwMirror, dwSwap, dwFlip);
+ return S_OK;
+}
+
+//<==================== VS-END ======================>
+
///////////////////////////////////////////////////////////
// all inherited virtual functions
///////////////////////////////////////////////////////////
@@ -398,7 +455,6 @@ HRESULT STDMETHODCALLTYPE CVCam::Run(REFERENCE_TIME tStart)
return hr;
}
-
HRESULT STDMETHODCALLTYPE CVCam::SetSyncSource(__in_opt IReferenceClock *pClock)
{
return CSource::SetSyncSource(pClock);
@@ -435,25 +491,45 @@ HRESULT STDMETHODCALLTYPE CVCam::Unregister( void)
}
//////////////////////////////////////////////////////////////////////////
+//<==================== VS-START ====================>
+HRESULT CVCamStream::put_Settings(DWORD dwFps, DWORD dwResolution, DWORD dwMirror, DWORD dwSwap, DWORD dwFlip)
+{
+ // Calling Disconnect() would disconnect SpoutCam with a connected filter (e.g. Color Space Converter) in GraphStudioNext,
+ // but something is still missing, you can't reconnect the existing filters with the new cam settings.
+ // For now, a better solution is to let the user select "Disconnect all Filters" from the "Graph" menu after SpoutCam's settings
+ // were changed by its Properties dialog, then rendering SpoutCam's output pin works fine and all filters are successfully reconnected,
+ // taking into account the new fps/resolution settings.
+ SetFps(dwFps);
+ SetResolution(dwResolution);
+ receiver.m_bMirror = (dwMirror > 0);
+ receiver.m_bSwapRB = (dwSwap > 0);
+ // Flip is true by default - so false will appear inverted
+ bInvert = !(dwFlip > 0);
+
+ return GetMediaType(0, &m_mt);
+}
+//<==================== VS-END ======================>
//////////////////////////////////////////////////////////////////////////
// CVCamStream is the one and only output pin of CVCam which handles
// all the stuff.
//////////////////////////////////////////////////////////////////////////
CVCamStream::CVCamStream(HRESULT *phr, CVCam *pParent, LPCWSTR pPinName) :
- CSourceStream(NAME(SpoutCamName), phr, pParent, pPinName), m_pParent(pParent)
+ CSourceStream(NAME(SPOUTCAMNAME), phr, pParent, pPinName), m_pParent(pParent) //VS: replaced SpoutCamName with makro SPOUTCAMNAME, NAME() expects LPCTSTR
{
- g_pd3dDevice = nullptr;
bDXinitialized = false; // DirectX
bMemoryMode = false; // Default mode is texture, true means memoryshare
bInvert = true; // Not currently used
bInitialized = false; // Spoutcam receiver
- bDisconnected = false; // Has to connect before can disconnect or it will never connect
g_Width = 640; // give it an initial size - this will be changed if a sender is running at start
g_Height = 480;
g_SenderName[0] = 0;
g_ActiveSender[0] = 0;
+
+ // Maximum precision of timeGetTime used in FillBuffer
+ timeGetDevCaps(&g_caps, sizeof(g_caps));
+ timeBeginPeriod(g_caps.wPeriodMin);
//
// Retrieve fps and resolution from registry "SpoutCamConfig"
@@ -466,13 +542,9 @@ CVCamStream::CVCamStream(HRESULT *phr, CVCam *pParent, LPCWSTR pPinName) :
// 50 4
// 60 5
//
- dwFps = 3; // Fps from SpoutCamConfig (default 3 = 30)
+ // Fps from SpoutCamConfig (default 3 = 30)
if (!ReadDwordFromRegistry(HKEY_CURRENT_USER, "Software\\Leading Edge\\SpoutCam", "fps", &dwFps)) {
dwFps = 3;
- g_FrameTime = 333333;
- }
- else {
- SetFps(dwFps);
}
// o Resolution
@@ -488,28 +560,10 @@ CVCamStream::CVCamStream(HRESULT *phr, CVCam *pParent, LPCWSTR pPinName) :
// 1280 x 1024 9
// 1920 x 1080 10
//
- dwResolution = 0; // Resolution from SpoutCamConfig (default 0 = active sender)
- ReadDwordFromRegistry(HKEY_CURRENT_USER, "Software\\Leading Edge\\SpoutCam", "resolution", &dwResolution);
-
- // If there is no Sender, getmediatype will use the resolution set by the user
-
- // Resolution index 0 means that the user has selected "Active sender"
- if(dwResolution == 0) {
- // Use the resolution of the active sender if one is running
- if(receiver.GetActiveSender(g_SenderName)) {
- unsigned int width, height;
- HANDLE sharehandle;
- DWORD format;
- if (receiver.GetSenderInfo(g_SenderName, width, height, sharehandle, format)) {
- // If not fixed to the a selected resolution, use the sender width and height
- g_Width = width;
- g_Height = height;
- }
- }
- }
- else {
- // Set g_Width and g_Height according to the registry setting
- SetResolution(dwResolution);
+ // Resolution from SpoutCamConfig (default 0 = active sender)
+ if (!ReadDwordFromRegistry(HKEY_CURRENT_USER, "Software\\Leading Edge\\SpoutCam", "resolution", &dwResolution))
+ {
+ dwResolution = 0;
}
// Find out whether memoryshare mode is selected by SpoutSettings
@@ -520,40 +574,30 @@ CVCamStream::CVCamStream(HRESULT *phr, CVCam *pParent, LPCWSTR pPinName) :
// Options in SpoutCamSettings
//
- DWORD dwMode = 0;
// Mirror image
- ReadDwordFromRegistry(HKEY_CURRENT_USER, "Software\\Leading Edge\\SpoutCam", "mirror", &dwMode);
- if(dwMode > 0)
- receiver.m_bMirror = true;
- else
- receiver.m_bMirror = false;
+ DWORD dwMirror = 0;
+ ReadDwordFromRegistry(HKEY_CURRENT_USER, "Software\\Leading Edge\\SpoutCam", "mirror", &dwMirror);
// RGB <> BGR
- ReadDwordFromRegistry(HKEY_CURRENT_USER, "Software\\Leading Edge\\SpoutCam", "swap", &dwMode);
- if (dwMode > 0)
- receiver.m_bSwapRB = true;
- else
- receiver.m_bSwapRB = false;
+ DWORD dwSwap = 0;
+ ReadDwordFromRegistry(HKEY_CURRENT_USER, "Software\\Leading Edge\\SpoutCam", "swap", &dwSwap);
// Flip image
// Default is flipped due to upside down Windows bitmap
// If set false, the result comes out inverted
// bInvert = false;
- ReadDwordFromRegistry(HKEY_CURRENT_USER, "Software\\Leading Edge\\SpoutCam", "flip", &dwMode);
- if (dwMode > 0)
- bInvert = false;
- else
- bInvert = true;
+ DWORD dwFlip = 0;
+ ReadDwordFromRegistry(HKEY_CURRENT_USER, "Software\\Leading Edge\\SpoutCam", "flip", &dwFlip);
- m_Fps = dwFps;
- m_Resolution = dwResolution;
-
- // Set mediatype to active sender width and height, or user defaults
- GetMediaType(0, &m_mt);
+ //<==================== VS-START ====================>
+ // Now always the new function put_Settings is called, which in turn calls
+ // SetFps and SetResolution. Dealing with resolution 0 (Sender) was moved to
+ // SetResolution.
+ put_Settings(dwFps, dwResolution, dwMirror, dwSwap, dwFlip);
+ //<==================== VS-END ======================>
NumDroppedFrames = 0LL;
NumFrames = 0LL;
- hwndButton = NULL; // ensure NULL of static variable for the OpenGL window handle
//
// Special purpose option : Lock to a specific sender
@@ -601,11 +645,33 @@ void CVCamStream::SetFps(DWORD dwFps)
}
}
-
void CVCamStream::SetResolution(DWORD dwResolution)
{
switch(dwResolution) {
+
+ //<==================== VS-START ====================>
// Case 0 - use the active sender or default
+ case 0:
+ {
+ // If there is no Sender, getmediatype will use the resolution set by the user
+ // Resolution index 0 means that the user has selected "Active sender"
+ // Use the resolution of the active sender if one is running
+ if (receiver.GetActiveSender(g_SenderName))
+ {
+ unsigned int width, height;
+ HANDLE sharehandle;
+ DWORD format;
+ if (receiver.GetSenderInfo(g_SenderName, width, height, sharehandle, format))
+ {
+ // If not fixed to the a selected resolution, use the sender width and height
+ g_Width = width;
+ g_Height = height;
+ }
+ }
+ }
+ break;
+ //<==================== VS-END ======================>
+
case 1 :
g_Width = 320; // 1
g_Height = 240;
@@ -661,6 +727,9 @@ CVCamStream::~CVCamStream()
if (bDXinitialized)
receiver.CleanupDX11();
+ // End timer precision
+ timeEndPeriod(g_caps.wPeriodMin);
+
}
HRESULT CVCamStream::QueryInterface(REFIID riid, void **ppv)
@@ -697,7 +766,7 @@ HRESULT CVCamStream::FillBuffer(IMediaSample *pms)
HRESULT hr = S_OK;
BYTE *pData = nullptr;
- VIDEOINFOHEADER *pvi = (VIDEOINFOHEADER *) m_mt.Format();
+ VIDEOINFOHEADER *pvi = (VIDEOINFOHEADER *)m_mt.Format();
// If graph is inactive stop cueing samples
if(!m_pParent->IsActive()) {
@@ -707,116 +776,130 @@ HRESULT CVCamStream::FillBuffer(IMediaSample *pms)
// Memory share mode not supported by DirectX
if (bMemoryMode)
return FALSE;
-
+
//
- // Simple timing modifed from original Vcam
- // https://github.com/johnmaccormick/MultiCam/blob/master/vcam/Filters.cpp
+ // Timing - modified from Red5 method
//
// Set the timestamps that will govern playback frame rate.
// The current time is the sample's start.
REFERENCE_TIME rtNow = m_rtLastTime;
- REFERENCE_TIME avgFrameTime = ((VIDEOINFOHEADER*)m_mt.pbFormat)->AvgTimePerFrame;
+ REFERENCE_TIME avgFrameTime = g_FrameTime; // Desired average frame time is set by the user
+
+ // Create some working info
+ REFERENCE_TIME rtDelta, rtDelta2 = 0LL; // delta for dropped, delta 2 for sleep.
+
+ //
+ // What time is it REALLY ???
+ //
+ m_pParent->GetSyncSource(&m_pClock);
+ if (m_pClock) {
+ m_pClock->GetTime(&refSync1);
+ m_pClock->Release();
+ }
+ else {
+ // Some programs do not implement the DirectShow clock and can crash if assumed
+ // so we can use TimeGetTime instead. Only millisecond precision, but so is Sleep.
+ refSync1 = (REFERENCE_TIME)timeGetTime() * 10000LL; // Cast before the multiply to avoid overflow
+ }
+
+ if (NumFrames <= 1) {
+ // initiate values
+ refStart = refSync1; // FirstFrame No Drop
+ refSync2 = 0;
+ }
+
rtNow = m_rtLastTime;
- // Sleep to meet the frame rate.
- // elapsed will be zero for the first call.
- REFERENCE_TIME elapsed = (REFERENCE_TIME)EndTiming() / 1000LL; // Sleep is msec precision
- if (elapsed < avgFrameTime / 10000LL) {
- DWORD dwSleep = (DWORD)(avgFrameTime / 10000LL - elapsed);
- Sleep(dwSleep);
+ m_rtLastTime = avgFrameTime + m_rtLastTime;
+
+ // IAMDropppedFrame. We only have avgFrameTime to generate image.
+ // Find generated stream time and compare to real elapsed time.
+ rtDelta = ((refSync1 - refStart) - (((NumFrames)*avgFrameTime) - avgFrameTime));
+ if (rtDelta - refSync2 < 0) {
+ // we are early
+ rtDelta2 = rtDelta - refSync2;
+ DWORD dwSleep = (DWORD)abs(rtDelta2 / 10000LL);
+ if (dwSleep >= 1)
+ Sleep(dwSleep);
}
- // Look for dropped frames and adjust.
- REFERENCE_TIME droppedFrames = elapsed / (avgFrameTime / 10000LL);
- if (droppedFrames > 0) {
+ else if (rtDelta / avgFrameTime > NumDroppedFrames) {
+ // new dropped frame
+ NumDroppedFrames = rtDelta / avgFrameTime;
+ // Figure new RT for sleeping
+ refSync2 = NumDroppedFrames * avgFrameTime;
// Our time stamping needs adjustment.
- // Find total real stream.
- rtNow = rtNow + droppedFrames * avgFrameTime;
+ // Find total real stream time from start time.
+ rtNow = refSync1 - refStart;
m_rtLastTime = rtNow + avgFrameTime;
pms->SetDiscontinuity(true);
}
- else {
- m_rtLastTime += avgFrameTime;
- }
// The SetTime method sets the stream times when this sample should begin and finish.
- pms->SetTime(&rtNow, &m_rtLastTime);
+ hr = pms->SetTime(&rtNow, &m_rtLastTime);
// Set true on every sample for uncompressed frames
- pms->SetSyncPoint(TRUE);
-
- // Reset timer for sleep calculations
- StartTiming();
+ hr = pms->SetSyncPoint(true);
+ // ============== END OF INITIAL TIMING ============
// Check access to the sample's data buffer
pms->GetPointer(&pData);
- if(pData == NULL)
+ if (pData == NULL) {
return NOERROR;
+ }
// Get the current frame size for texture transfers
imagesize = (unsigned int)pvi->bmiHeader.biSizeImage;
width = (unsigned int)pvi->bmiHeader.biWidth;
height = (unsigned int)pvi->bmiHeader.biHeight;
- if(width == 0 || height == 0)
+ if (width == 0 || height == 0) {
return NOERROR;
+ }
- // Don't do anything if disconnected because it will already have connected
- // previously and something has changed. It can only disconnect after it has connected.
- if(!bDisconnected) {
-
- // If connected, sizes should be OK, but check again
- unsigned int size = (unsigned int)pms->GetSize();
- imagesize = width*height*3; // Retrieved above
- if(size != imagesize) {
- receiver.ReleaseReceiver();
- bDisconnected = true; // don't try again
- return NOERROR;
- }
-
- // Quit if nothing running at all
- if(!receiver.GetActiveSender(g_ActiveSender)) {
- receiver.ReleaseReceiver();
- goto ShowStatic;
- }
+ // Sizes should be OK, but check again
+ unsigned int size = (unsigned int)pms->GetSize();
+ // TODO : check imagesize = width*height*3;
+ if(size != imagesize) { // imagesize retrieved above
+ ReleaseCamReceiver();
+ goto ShowStatic;
+ }
- // Initialize DirectX if is has not been done
- if(!bDXinitialized) {
- if (receiver.OpenDirectX11()) {
- g_pd3dDevice = receiver.GetDevice();
- }
- else {
- bDXinitialized = false;
- bDisconnected = true; // don't try again
- return NOERROR;
- }
- } // endif !bDXinitialized
- bDXinitialized = true;
+ // Quit if nothing running at all
+ if(!receiver.GetActiveSender(g_ActiveSender)) {
+ ReleaseCamReceiver();
+ goto ShowStatic;
+ }
- // Get bgr pixels from the sender bgra shared texture
- // ReceiveImage handles sender detection, connection and copy of pixels
- if (receiver.ReceiveImage(pData, g_Width, g_Height, true, bInvert)) {
- // rgb(not rgba) = true, invert = true
- // If IsUpdated() returns true, the sender has changed
- if (receiver.IsUpdated()) {
- if (strcmp(g_SenderName, receiver.GetSenderName()) != 0) {
- // Only test for change of sender name.
- // The pixel buffer (pData) remains the same size and
- // ReceiveImage uses resampling for a different texture size
- strcpy_s(g_SenderName, 256, receiver.GetSenderName());
- // Set the sender name to the registry for SpoutCamSettings
- WritePathToRegistry(HKEY_CURRENT_USER, "Software\\Leading Edge\\SpoutCam", "sendername", g_SenderName);
- }
- }
- bInitialized = true;
- NumFrames++;
+ // Initialize DirectX if is has not been done
+ // TODO : prevent retries
+ if(!bDXinitialized) {
+ if (!receiver.OpenDirectX11()) {
return NOERROR;
}
- else {
- if (bInitialized) {
- receiver.ReleaseReceiver();
- bInitialized = false;
+ bDXinitialized = true;
+ } // endif !bDXinitialized
+
+ // DirectX is initialized OK
+ // Get bgr pixels from the sender bgra shared texture
+ // ReceiveImage handles sender detection, connection and copy of pixels
+ if (receiver.ReceiveImage(pData, g_Width, g_Height, true, bInvert)) {
+ // rgb(not rgba) = true, invert = true
+ // If IsUpdated() returns true, the sender has changed
+ if (receiver.IsUpdated()) {
+ if (strcmp(g_SenderName, receiver.GetSenderName()) != 0) {
+ // Only test for change of sender name.
+ // The pixel buffer (pData) remains the same size and
+ // ReceiveImage uses resampling for a different texture size
+ strcpy_s(g_SenderName, 256, receiver.GetSenderName());
+ // Set the sender name to the registry for SpoutCamSettings
+ WritePathToRegistry(HKEY_CURRENT_USER, "Software\\Leading Edge\\SpoutCam", "sendername", g_SenderName);
}
}
-
- } // endif not disconnected
+ bInitialized = true;
+ NumFrames++;
+ return NOERROR;
+ }
+ else {
+ ReleaseCamReceiver();
+ }
ShowStatic :
@@ -833,6 +916,16 @@ ShowStatic :
} // FillBuffer
+// Conditionally release receiver and reset flag
+void CVCamStream::ReleaseCamReceiver()
+{
+ if (bInitialized) {
+ receiver.ReleaseReceiver();
+ bInitialized = false;
+ }
+}
+
+
//
// Notify
// Ignore quality management messages sent from the downstream filter
@@ -867,7 +960,6 @@ HRESULT CVCamStream::GetMediaType(int iPosition, CMediaType *pmt)
if (iPosition > 1) {
return VFW_S_NO_MORE_ITEMS;
}
-
DECLARE_PTR(VIDEOINFOHEADER, pvi, pmt->AllocFormatBuffer(sizeof(VIDEOINFOHEADER)));
ZeroMemory(pvi, sizeof(VIDEOINFOHEADER));
@@ -911,7 +1003,6 @@ HRESULT CVCamStream::GetMediaType(int iPosition, CMediaType *pmt)
const GUID SubTypeGUID = GetBitmapSubtype(&pvi->bmiHeader);
pmt->SetSubtype(&SubTypeGUID);
pmt->SetVariableSize(); // LJ - to be checked
-
pmt->SetSampleSize(pvi->bmiHeader.biSizeImage);
return NOERROR;
@@ -1061,8 +1152,8 @@ HRESULT STDMETHODCALLTYPE CVCamStream::GetStreamCaps(int iIndex, AM_MEDIA_TYPE *
// For a capture filter, the size is the largest signal the filter
// can digitize with every pixel remaining unique.
// Note Deprecated.
- pvscc->InputSize.cx = 1920;
- pvscc->InputSize.cy = 1080;
+ pvscc->InputSize.cx = (LONG)width; // 1920;
+ pvscc->InputSize.cy = (LONG)height; // 1080;
pvscc->MinCroppingSize.cx = 0; // LJ was 80 but we don't want to limit it
pvscc->MinCroppingSize.cy = 0; // was 60
pvscc->MaxCroppingSize.cx = 1920;
diff --git a/source/cam.def b/source/cam.def
index 591a4dd..b562402 100644
--- a/source/cam.def
+++ b/source/cam.def
@@ -5,3 +5,4 @@ EXPORTS
DllCanUnloadNow PRIVATE
DllRegisterServer PRIVATE
DllUnregisterServer PRIVATE
+ Configure PRIVATE
diff --git a/source/cam.h b/source/cam.h
index 19e3de2..08b3cda 100644
--- a/source/cam.h
+++ b/source/cam.h
@@ -8,6 +8,7 @@
// 28.09.15 - updated with modifications by John MacCormick, 2012.
// 10.07.16 - Modified for "SpoutCamConfig" for starting fps and resolution
// 17.10.19 - Clean up for DirectX methods
+// 13.10.20 - Clean up unused variables
//
#pragma once
@@ -16,7 +17,6 @@
#define DECLARE_PTR(type, ptr, expr) type* ptr = (type*)(expr);
-
// leak checking
// http://www.codeproject.com/Articles/9815/Visual-Leak-Detector-Enhanced-Memory-Leak-Detectio
//
@@ -31,11 +31,41 @@
#include "..\SpoutDX\source\SpoutDX.h"
#include
+// LJ DEBUG
+#include
+#include
+using namespace std::chrono_literals;
+
+//<==================== VS-START ====================>
+#include "dshowutil.h"
+
+// we need a LPCTSTR for the NAME() makros in debug mode
+#define SPOUTCAMNAME "SpoutCam"
+
+extern "C" {
+ DECLARE_INTERFACE_(ICamSettings, IUnknown)
+ {
+ STDMETHOD(put_Settings) (THIS_
+ DWORD dwFps,
+ DWORD dwResolution,
+ DWORD dwMirror,
+ DWORD dwSwap,
+ DWORD dwFlip
+ ) PURE;
+ };
+}
+
+EXTERN_C const GUID CLSID_SpoutCamPropertyPage;
+EXTERN_C const GUID IID_ICamSettings;
+//<==================== VS-END ======================>
+
EXTERN_C const GUID CLSID_SpoutCam;
EXTERN_C const WCHAR SpoutCamName[MAX_PATH];
class CVCamStream;
-class CVCam : public CSource
+class CVCam : public CSource,
+ public ISpecifyPropertyPages,//VS
+ public ICamSettings//VS
{
public:
//////////////////////////////////////////////////////////////////////////
@@ -51,6 +81,14 @@ class CVCam : public CSource
STDMETHODIMP JoinFilterGraph(__inout_opt IFilterGraph * pGraph, __in_opt LPCWSTR pName);
+ //<==================== VS-START ====================>
+ // ISpecifyPropertyPages interface
+ STDMETHODIMP GetPages(CAUUID *pPages);
+
+ // ICamSettings interface
+ STDMETHODIMP put_Settings(DWORD dwFps, DWORD dwResolution, DWORD dwMirror, DWORD dwSwap, DWORD dwFlip);
+ //<==================== VS-END ======================>
+
private:
CVCam(LPUNKNOWN lpunk, HRESULT *phr);
@@ -162,13 +200,11 @@ class CVCamStream : public CSourceStream, public IAMStreamConfig, public IKsProp
HRESULT GetMediaType(int iPosition, CMediaType *pmt);
HRESULT SetMediaType(const CMediaType *pmt);
HRESULT OnThreadCreate(void);
-
- int m_Fps;
- int m_Resolution;
- bool m_bLock;
+ HRESULT put_Settings(DWORD dwFps, DWORD dwResolution, DWORD dwMirror, DWORD dwSwap, DWORD dwFlip); //VS
void SetFps(DWORD dwFps);
void SetResolution(DWORD dwResolution);
+ void ReleaseCamReceiver();
// ============== IPC functions ==============
//
@@ -179,23 +215,18 @@ class CVCamStream : public CSourceStream, public IAMStreamConfig, public IKsProp
char g_SenderName[256];
char g_ActiveSender[256]; // The name of any Spout sender being received
- ID3D11Device* g_pd3dDevice; // DirectX 11.0 device pointer
bool bMemoryMode; // true = memory, false = texture
bool bInvert;
- bool bDebug;
bool bInitialized;
bool bDXinitialized;
- bool bDisconnected; // Sender had started but it has stopped or changed image size
-
- GLenum glBGRmode;
unsigned int g_Width; // The global filter image width
unsigned int g_Height; // The global filter image height
DWORD dwFps; // Fps from SpoutCamConfig
DWORD dwResolution; // Resolution from SpoutCamConfig
- DWORD dwLock; // Fix to the selected resolution
int g_FrameTime; // Frame time to use based on fps selection
+ TIMECAPS g_caps; // Timer capability for Sleep precision
private:
diff --git a/source/camprops.cpp b/source/camprops.cpp
new file mode 100644
index 0000000..f3f2d48
--- /dev/null
+++ b/source/camprops.cpp
@@ -0,0 +1,268 @@
+#include
+#include
+#include
+#include
+
+#include "cam.h"
+
+#include "camprops.h"
+#include "resource.h"
+#include "version.h"
+
+// CreateInstance
+// Used by the DirectShow base classes to create instances
+CUnknown *CSpoutCamProperties::CreateInstance(LPUNKNOWN lpunk, HRESULT *phr)
+{
+ ASSERT(phr);
+ CUnknown *punk = new CSpoutCamProperties(lpunk, phr);
+ if (punk == NULL)
+ {
+ if (phr)
+ *phr = E_OUTOFMEMORY;
+ }
+ return punk;
+} // CreateInstance
+
+// Constructor
+CSpoutCamProperties::CSpoutCamProperties(LPUNKNOWN pUnk, HRESULT *phr) :
+ CBasePropertyPage("Settings", pUnk, IDD_SPOUTCAMBOX, IDS_TITLE),
+ m_pCamSettings(NULL),
+ m_bIsInitialized(FALSE)
+{
+ ASSERT(phr);
+ if (phr)
+ *phr = S_OK;
+}
+
+//OnReceiveMessage is called when the dialog receives a window message.
+INT_PTR CSpoutCamProperties::OnReceiveMessage(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ switch (uMsg)
+ {
+ case WM_COMMAND:
+ {
+ if (m_bIsInitialized)
+ {
+ m_bDirty = TRUE;
+ if (m_pPageSite)
+ {
+ m_pPageSite->OnStatusChange(PROPPAGESTATUS_DIRTY);
+ }
+ }
+ return (LRESULT)1;
+ }
+ }
+
+ // Let the parent class handle the message.
+ return CBasePropertyPage::OnReceiveMessage(hwnd, uMsg, wParam, lParam);
+}
+
+//OnConnect is called when the client creates the property page. It sets the IUnknown pointer to the filter.
+HRESULT CSpoutCamProperties::OnConnect(IUnknown *pUnknown)
+{
+ TRACE("OnConnect");
+ CheckPointer(pUnknown, E_POINTER);
+ ASSERT(m_pCamSettings == NULL);
+
+ HRESULT hr = pUnknown->QueryInterface(IID_ICamSettings, (void **)&m_pCamSettings);
+ if (FAILED(hr))
+ {
+ return E_NOINTERFACE;
+ }
+
+ // Get the initial image FX property
+ CheckPointer(m_pCamSettings, E_FAIL);
+
+ m_bIsInitialized = FALSE;
+
+ return S_OK;
+}
+
+//OnDisconnect is called when the user dismisses the property sheet.
+HRESULT CSpoutCamProperties::OnDisconnect()
+{
+ TRACE("OnDisconnect");
+ // Release of Interface after setting the appropriate old effect value
+ if (m_pCamSettings)
+ {
+ m_pCamSettings->Release();
+ m_pCamSettings = NULL;
+ }
+
+ return S_OK;
+}
+
+//OnActivate is called when the dialog is created.
+HRESULT CSpoutCamProperties::OnActivate()
+{
+ TRACE("OnActivate");
+
+ HWND hwndCtl;
+ DWORD dwValue;
+
+ ////////////////////////////////////////
+ // Fps
+ ////////////////////////////////////////
+
+ // Retrieve fps from registry
+ if (!ReadDwordFromRegistry(HKEY_CURRENT_USER, "Software\\Leading Edge\\SpoutCam", "fps", &dwValue) || dwValue > 5)
+ {
+ dwValue = 3; // default 3 = 30 fps
+ }
+
+ hwndCtl = GetDlgItem(this->m_Dlg, IDC_FPS);
+
+ WCHAR fps_values[6][3] =
+ {
+ L"10",
+ L"15",
+ L"25",
+ L"30", // default
+ L"50",
+ L"60"
+ };
+
+ WCHAR fps[3];
+ int k = 0;
+
+ memset(&fps, 0, sizeof(fps));
+ for (k = 0; k <= 5; k += 1)
+ {
+ wcscpy_s(fps, sizeof(fps) / sizeof(WCHAR), fps_values[k]);
+ ComboBox_AddString(hwndCtl, fps);
+ }
+
+ // Select default
+ ComboBox_SetCurSel(hwndCtl, dwValue);
+
+ ////////////////////////////////////////
+ // Resolution
+ ////////////////////////////////////////
+
+ // Retrieve resolution from registry "SpoutCamConfig"
+ if (!ReadDwordFromRegistry(HKEY_CURRENT_USER, "Software\\Leading Edge\\SpoutCam", "resolution", &dwValue) || dwValue > 10)
+ {
+ dwValue = 0; // default 0 = active sender
+ }
+
+ hwndCtl = GetDlgItem(this->m_Dlg, IDC_RESOLUTION);
+
+ WCHAR res_values[11][14] =
+ {
+ L"Active Sender",
+ L"320 x 240",
+ L"640 x 360",
+ L"640 x 480", // default if no sender
+ L"800 x 600",
+ L"1024 x 720",
+ L"1024 x 768",
+ L"1280 x 720",
+ L"1280 x 960",
+ L"1280 x 1024",
+ L"1920 x 1080"
+ };
+
+ WCHAR res[14];
+ memset(&res, 0, sizeof(res));
+ for (k = 0; k <= 10; k += 1)
+ {
+ wcscpy_s(res, sizeof(res) / sizeof(WCHAR), res_values[k]);
+ ComboBox_AddString(hwndCtl, res);
+ }
+
+ // Select current value
+ ComboBox_SetCurSel(hwndCtl, dwValue);
+
+ ////////////////////////////////////////
+ // Mirror image
+ ////////////////////////////////////////
+ if (!ReadDwordFromRegistry(HKEY_CURRENT_USER, "Software\\Leading Edge\\SpoutCam", "mirror", &dwValue))
+ {
+ dwValue = 0;
+ }
+ hwndCtl = GetDlgItem(this->m_Dlg, IDC_MIRROR);
+ Button_SetCheck(hwndCtl, dwValue);
+
+ ////////////////////////////////////////
+ // Swap RGB <> BGR
+ ////////////////////////////////////////
+ if (!ReadDwordFromRegistry(HKEY_CURRENT_USER, "Software\\Leading Edge\\SpoutCam", "swap", &dwValue))
+ {
+ dwValue = 0;
+ }
+ hwndCtl = GetDlgItem(this->m_Dlg, IDC_SWAP);
+ Button_SetCheck(hwndCtl, dwValue);
+
+ ////////////////////////////////////////
+ // Flip image
+ // Default is flipped due to upside down Windows bitmap
+ // If set false, the result comes out inverted
+ // bInvert = false;
+ ////////////////////////////////////////
+ if (!ReadDwordFromRegistry(HKEY_CURRENT_USER, "Software\\Leading Edge\\SpoutCam", "flip", &dwValue))
+ {
+ dwValue = 0;
+ }
+ hwndCtl = GetDlgItem(this->m_Dlg, IDC_FLIP);
+ Button_SetCheck(hwndCtl, dwValue);
+
+ // Show SpoutCam version
+ hwndCtl = GetDlgItem(this->m_Dlg, IDC_VERS);
+ Static_SetText(hwndCtl, L"Version: " _VER_VERSION_STRING);
+
+ m_bIsInitialized = TRUE;
+
+ if (m_pPageSite)
+ {
+ m_pPageSite->OnStatusChange(PROPPAGESTATUS_DIRTY);
+ }
+
+ return S_OK;
+}
+
+//OnApplyChanges is called when the user commits the property changes by clicking the OK or Apply button.
+HRESULT CSpoutCamProperties::OnApplyChanges()
+{
+ TRACE("OnApplyChanges");
+
+ DWORD dwFps, dwResolution, dwMirror, dwSwap, dwFlip;
+
+ // =================================
+ // Get old fps and resolution for user warning
+ DWORD dwOldFps, dwOldResolution;
+ ReadDwordFromRegistry(HKEY_CURRENT_USER, "Software\\Leading Edge\\SpoutCam", "fps", &dwOldFps);
+ ReadDwordFromRegistry(HKEY_CURRENT_USER, "Software\\Leading Edge\\SpoutCam", "resolution", &dwOldResolution);
+ dwFps = ComboBox_GetCurSel(GetDlgItem(this->m_Dlg, IDC_FPS));
+ dwResolution = ComboBox_GetCurSel(GetDlgItem(this->m_Dlg, IDC_RESOLUTION));
+ if (dwOldFps != dwFps || dwOldResolution != dwResolution) {
+ if (MessageBoxA(NULL, "For change of resolution or fps, you\nhave to stop and re-start SpoutCam\nDo you want to change ? ", "Warning", MB_YESNO | MB_TOPMOST | MB_ICONQUESTION) == IDNO) {
+ if (dwOldFps != dwFps)
+ ComboBox_SetCurSel(GetDlgItem(this->m_Dlg, IDC_FPS), dwOldFps);
+ if (dwOldResolution != dwResolution)
+ ComboBox_SetCurSel(GetDlgItem(this->m_Dlg, IDC_RESOLUTION), dwOldResolution);
+ return -1;
+ }
+ }
+
+ // =================================
+
+ WriteDwordToRegistry(HKEY_CURRENT_USER, "Software\\Leading Edge\\SpoutCam", "fps", dwFps);
+ WriteDwordToRegistry(HKEY_CURRENT_USER, "Software\\Leading Edge\\SpoutCam", "resolution", dwResolution);
+
+ HWND hwndCtl = GetDlgItem(this->m_Dlg, IDC_MIRROR);
+ dwMirror = Button_GetCheck(hwndCtl);
+ WriteDwordToRegistry(HKEY_CURRENT_USER, "Software\\Leading Edge\\SpoutCam", "mirror", dwMirror);
+
+ hwndCtl = GetDlgItem(this->m_Dlg, IDC_SWAP);
+ dwSwap = Button_GetCheck(hwndCtl);
+ WriteDwordToRegistry(HKEY_CURRENT_USER, "Software\\Leading Edge\\SpoutCam", "swap", dwSwap);
+
+ hwndCtl = GetDlgItem(this->m_Dlg, IDC_FLIP);
+ dwFlip = Button_GetCheck(hwndCtl);
+ WriteDwordToRegistry(HKEY_CURRENT_USER, "Software\\Leading Edge\\SpoutCam", "flip", dwFlip);
+
+ if (m_pCamSettings)
+ m_pCamSettings->put_Settings(dwFps, dwResolution, dwMirror, dwSwap, dwFlip);
+
+ return S_OK;
+}
diff --git a/source/camprops.h b/source/camprops.h
new file mode 100644
index 0000000..951dca8
--- /dev/null
+++ b/source/camprops.h
@@ -0,0 +1,24 @@
+#include
+
+class CSpoutCamProperties : public CBasePropertyPage
+{
+
+public:
+
+ static CUnknown * WINAPI CreateInstance(LPUNKNOWN lpunk, HRESULT *phr);
+
+private:
+
+ INT_PTR OnReceiveMessage(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
+ HRESULT OnConnect(IUnknown *pUnknown);
+ HRESULT OnDisconnect();
+ HRESULT OnActivate();
+ //HRESULT OnDeactivate();
+ HRESULT OnApplyChanges();
+
+ CSpoutCamProperties(LPUNKNOWN lpunk, HRESULT *phr);
+
+ BOOL m_bIsInitialized; // Used to ignore startup messages
+ ICamSettings *m_pCamSettings; // The custom interface on the filter
+};
+
diff --git a/source/dialog.aps b/source/dialog.aps
new file mode 100644
index 0000000..0d50eab
Binary files /dev/null and b/source/dialog.aps differ
diff --git a/source/dialog.rc b/source/dialog.rc
new file mode 100644
index 0000000..4121fa8
--- /dev/null
+++ b/source/dialog.rc
@@ -0,0 +1,38 @@
+// Generated by ResEdit 1.6.6
+// Copyright (C) 2006-2015
+// http://www.resedit.net
+
+#include
+#include "resource.h"
+
+//
+// Dialog resources
+//
+LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL
+IDD_SPOUTCAMBOX DIALOG 0, 0, 211, 140
+STYLE DS_CENTER | DS_SHELLFONT | WS_CHILD
+FONT 8, "Ms Shell Dlg"
+{
+ GROUPBOX "Frame Rate (fps)", -1, 13, 7, 185, 32, 0, WS_EX_LEFT
+ COMBOBOX IDC_FPS, 28, 19, 100, 100, WS_TABSTOP | CBS_DROPDOWN | CBS_HASSTRINGS, WS_EX_LEFT
+
+ GROUPBOX "Starting Resolution", -1, 13, 44, 185, 32, 0, WS_EX_LEFT
+ COMBOBOX IDC_RESOLUTION, 28, 56, 100, 120, WS_TABSTOP | CBS_DROPDOWN | CBS_HASSTRINGS, WS_EX_LEFT
+
+ GROUPBOX "Options", -1, 13, 81, 185, 28, 0, WS_EX_LEFT
+ AUTOCHECKBOX "Mirror", IDC_MIRROR, 28, 93, 36, 8, 0, WS_EX_LEFT
+ AUTOCHECKBOX "Flip", IDC_FLIP, 65, 93, 36, 8, 0, WS_EX_LEFT
+ AUTOCHECKBOX "Swap", IDC_SWAP, 102, 93, 36, 8, 0, WS_EX_LEFT
+
+ LTEXT "Version:", IDC_VERS, 15, 120, 130, 9, SS_LEFT, WS_EX_LEFT
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// String Table
+//
+
+STRINGTABLE
+BEGIN
+ IDS_TITLE "Settings"
+END
diff --git a/source/dll.cpp b/source/dll.cpp
index 46613fe..84c8638 100644
--- a/source/dll.cpp
+++ b/source/dll.cpp
@@ -13,32 +13,33 @@
#pragma comment(lib, "ole32")
#pragma comment(lib, "oleaut32")
-//////////////////////////////////////////////////////////////////////////
-// Valentin Schmidt 2018-07-24: this project uses a version called "BaseClasses.lib" instead
-//////////////////////////////////////////////////////////////////////////
+#include "cam.h"
-//#ifdef _DEBUG
-// #pragma comment(lib, "strmbasd")
-//#else
-// #pragma comment(lib, "strmbase")
-//#endif
+//<==================== VS-START ====================>
+#ifdef DIALOG_WITHOUT_REGISTRATION
+#pragma comment(lib, "Comctl32.lib")
+#endif
-#include "cam.h"
+#include "camprops.h"
+//<==================== VS-END ======================>
#include
#include
#include
-
#define CreateComObject(clsid, iid, var) CoCreateInstance( clsid, NULL, CLSCTX_INPROC_SERVER, iid, (void **)&var);
-STDAPI AMovieSetupRegisterServer( CLSID clsServer, LPCWSTR szDescription, LPCWSTR szFileName, LPCWSTR szThreadingModel = L"Both", LPCWSTR szServerType = L"InprocServer32" );
+STDAPI AMovieSetupRegisterServer( CLSID clsServer, LPCWSTR szDescription, LPCWSTR szFileName, LPCWSTR szThreadingModel = L"Both", LPCWSTR szServerType = L"InprocServer32" );
STDAPI AMovieSetupUnregisterServer( CLSID clsServer );
//
// The NAME OF THE CAMERA CAN BE CHANGED HERE
-//
-const WCHAR SpoutCamName[] = L"SpoutCam";
+//<==================== VS-START ====================>
+// Actual name now defined as LPCTSTR literal makro in cam.h.
+// This was needed because the NAME() makros in debug mode need a LPCTSTR,
+// and therefor the debug version previously didn't compile.
+const WCHAR SpoutCamName[] = L"" SPOUTCAMNAME;
+//<==================== VS-END ======================>
//
// THE CLSID CAN BE CHANGED HERE
@@ -64,16 +65,17 @@ const WCHAR SpoutCamName[] = L"SpoutCam";
//
DEFINE_GUID(CLSID_SpoutCam, 0x8e14549a, 0xdb61, 0x4309, 0xaf, 0xa1, 0x35, 0x78, 0xe9, 0x27, 0xe9, 0x33);
+//<==================== VS-START ====================>
+// And the property page we support
-//
-// TODO
-//
-// SpoutCam filter property page
-// https://msdn.microsoft.com/en-us/library/dd377627%28v=vs.85%29.aspx
-//
-// F0B09553-7B71-49D5-9E04-9DE0A0987144
-// DEFINE_GUID(CLSID_SaturationProp, 0xF0B09553, 0x7B71, 0x49D5, 0x9E, 0x04, 0x9D, 0xE0, 0xA0, 0x98, 0x71, 0x44);
+// {CD7780B7-40D2-4F33-80E2-B02E009CE63F}
+DEFINE_GUID(CLSID_SpoutCamPropertyPage,
+ 0xcd7780b7, 0x40d2, 0x4f33, 0x80, 0xe2, 0xb0, 0x2e, 0x0, 0x9c, 0xe6, 0x3f);
+// {6CD0D97B-C242-48DA-916D-28856D00754B}
+DEFINE_GUID(IID_ICamSettings,
+ 0x6cd0d97b, 0xc242, 0x48da, 0x91, 0x6d, 0x28, 0x85, 0x6d, 0x00, 0x75, 0x4b);
+//<==================== VS-END ======================>
const AMOVIESETUP_MEDIATYPE AMSMediaTypesVCam =
{
@@ -113,7 +115,14 @@ CFactoryTemplate g_Templates[] =
NULL,
&AMSFilterVCam
}
-
+ //<==================== VS-START ====================>
+ ,
+ {
+ L"Settings",
+ &CLSID_SpoutCamPropertyPage,
+ CSpoutCamProperties::CreateInstance
+ }
+ //<==================== VS-END ======================>
};
int g_cTemplates = sizeof(g_Templates) / sizeof(g_Templates[0]);
@@ -131,21 +140,23 @@ HRESULT RegisterFilter(
STDAPI RegisterFilters( BOOL bRegister )
{
+ ASSERT(g_hInst != 0);
+
HRESULT hr = NOERROR;
WCHAR achFileName[MAX_PATH];
- char achTemp[MAX_PATH];
- ASSERT(g_hInst != 0);
-
- if( 0 == GetModuleFileNameA(g_hInst, achTemp, sizeof(achTemp)))
- return AmHresultFromWin32(GetLastError());
+
+ //<==================== VS-START ====================>
+ if (0 == GetModuleFileNameW(g_hInst, achFileName, sizeof(achFileName)))
+ {
+ return AmHresultFromWin32(GetLastError());
+ }
+ //<==================== VS-END ====================>
- MultiByteToWideChar(CP_ACP, 0L, achTemp, lstrlenA(achTemp) + 1,
- achFileName, NUMELMS(achFileName));
-
hr = CoInitialize(0);
if(bRegister)
{
hr = AMovieSetupRegisterServer(CLSID_SpoutCam, SpoutCamName, achFileName, L"Both", L"InprocServer32");
+ hr = AMovieSetupRegisterServer(CLSID_SpoutCamPropertyPage, L"Settings", achFileName, L"Both", L"InprocServer32"); //VS
}
if( SUCCEEDED(hr) )
@@ -171,13 +182,15 @@ STDAPI RegisterFilters( BOOL bRegister )
}
// release interface
- //
if(fm)
fm->Release();
}
- if( SUCCEEDED(hr) && !bRegister )
- hr = AMovieSetupUnregisterServer( CLSID_SpoutCam );
+ if (SUCCEEDED(hr) && !bRegister)
+ {
+ hr = AMovieSetupUnregisterServer(CLSID_SpoutCam);
+ hr = AMovieSetupUnregisterServer(CLSID_SpoutCamPropertyPage); //VS
+ }
CoFreeUnusedLibraries();
CoUninitialize();
@@ -188,13 +201,80 @@ STDAPI RegisterFilters( BOOL bRegister )
// see http://msdn.microsoft.com/en-us/library/windows/desktop/dd376682%28v=vs.85%29.aspx
STDAPI DllRegisterServer()
{
- return RegisterFilters(TRUE); // && AMovieDllRegisterServer2( TRUE ); (from screencapturerecorder)
+ return RegisterFilters(TRUE);
}
STDAPI DllUnregisterServer()
{
- return RegisterFilters(FALSE); // && AMovieDllRegisterServer2( TRUE ); (from screencapturerecorder)
+ return RegisterFilters(FALSE);
+}
+
+//<==================== VS-START ====================>
+#ifdef DIALOG_WITHOUT_REGISTRATION
+
+extern "C" {
+ HRESULT WINAPI OleCreatePropertyFrameDirect(
+ HWND hwndOwner,
+ LPCOLESTR lpszCaption,
+ LPUNKNOWN* ppUnk,
+ IPropertyPage * page);
+}
+
+HRESULT ShowFilterPropertyPageDirect(IBaseFilter *pFilter, HWND hwndParent)
+{
+ TRACE("ShowFilterPropertyPageDirect");
+ HRESULT hr;
+ if (!pFilter)
+ return E_POINTER;
+ CSpoutCamProperties * pPage = (CSpoutCamProperties *)CSpoutCamProperties::CreateInstance(pFilter, &hr);
+ if (SUCCEEDED(hr))
+ {
+ hr = OleCreatePropertyFrameDirect(
+ hwndParent, // Parent window
+ SpoutCamName, // Caption for the dialog box
+ (IUnknown **)&pFilter, // Pointer to the filter
+ pPage
+ );
+ pPage->Release();
+ }
+ return hr;
+}
+#endif
+
+void CALLBACK Configure()
+{
+ HRESULT hr;
+ IBaseFilter *pFilter;
+ CUnknown *pInstance;
+
+ CoInitialize(nullptr);
+
+ // Obtain the filter's IBaseFilter interface.
+ pInstance = CVCam::CreateInstance(nullptr, &hr);
+ if (SUCCEEDED(hr))
+ {
+ hr = pInstance->NonDelegatingQueryInterface(IID_IBaseFilter, (void **)&pFilter);
+ if (SUCCEEDED(hr))
+ {
+ // If the filter is registered, this will open the settings dialog.
+ hr = ShowFilterPropertyPage(pFilter, GetDesktopWindow());
+
+#ifdef DIALOG_WITHOUT_REGISTRATION
+ if (FAILED(hr))
+ {
+ // The filter propably isn't registered in the system;
+ // This will open the settings dialog anyway.
+ hr = ShowFilterPropertyPageDirect(pFilter, GetDesktopWindow());
+ }
+#endif
+ pFilter->Release();
+ }
+ delete pInstance;
+ }
+
+ CoUninitialize();
}
+//<==================== VS-END ======================>
extern "C" BOOL WINAPI DllEntryPoint(HINSTANCE, ULONG, LPVOID);
diff --git a/source/dshowutil.h b/source/dshowutil.h
new file mode 100644
index 0000000..366860e
--- /dev/null
+++ b/source/dshowutil.h
@@ -0,0 +1,101 @@
+//////////////////////////////////////////////////////////////////////////
+// DSUtil.h: DirectShow helper functions.
+//
+// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
+// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
+// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+//////////////////////////////////////////////////////////////////////////
+
+// Conventions:
+//
+// Functions named "IsX" return true or false.
+//
+// Functions named "FindX" enumerate over a collection and return the first
+// matching instance.
+
+#pragma once
+
+#include
+#include
+#include
+
+#ifndef ASSERT
+#define ASSERT assert
+#endif
+
+#ifndef SAFE_RELEASE
+#define SAFE_RELEASE(x) if (x) { x->Release(); x = NULL; }
+#endif
+
+#ifndef CHECK_HR
+#define CHECK_HR(hr) if (FAILED(hr)) { goto done; }
+#endif
+
+#define TRACE(_x_) ((void)0)
+
+////////////////////////////////////////
+// uncomment the below to trace to debug log, monitor with Sysinternals' DebugView
+////////////////////////////////////////
+//inline void OutputDebugStringf(const char * format, ...)
+//{
+// char buffer[512];
+// va_list args;
+// va_start(args, format);
+// vsnprintf(buffer, 512, format, args);
+// OutputDebugStringA(buffer);
+// va_end(args);
+//}
+//#define TRACE(...) OutputDebugStringf(__VA_ARGS__);
+
+///////////////////////////////////////////////////////////////////////
+// Name: ShowFilterPropertyPage
+// Desc: Show a filter's property page.
+//
+// hwndParent: Owner window for the property page.
+///////////////////////////////////////////////////////////////////////
+inline HRESULT ShowFilterPropertyPage(IBaseFilter *pFilter, HWND hwndParent)
+{
+ HRESULT hr;
+
+ ISpecifyPropertyPages *pSpecify = NULL;
+ FILTER_INFO FilterInfo;
+ CAUUID caGUID;
+
+ if (!pFilter)
+ {
+ return E_POINTER;
+ }
+
+ ZeroMemory(&FilterInfo, sizeof(FilterInfo));
+ ZeroMemory(&caGUID, sizeof(caGUID));
+
+ // Discover if this filter contains a property page
+ CHECK_HR(hr = pFilter->QueryInterface(IID_ISpecifyPropertyPages, (void **)&pSpecify));
+ CHECK_HR(hr = pFilter->QueryFilterInfo(&FilterInfo));
+ CHECK_HR(hr = pSpecify->GetPages(&caGUID));
+
+ // Display the filter's property page
+ CHECK_HR(hr = OleCreatePropertyFrame(
+ hwndParent, // Parent window
+ 0, // x (Reserved)
+ 0, // y (Reserved)
+ FilterInfo.achName, // Caption for the dialog box
+ 1, // Number of filters
+ (IUnknown **)&pFilter, // Pointer to the filter
+ caGUID.cElems, // Number of property pages
+ caGUID.pElems, // Pointer to property page CLSIDs
+ 0, // Locale identifier
+ 0, // Reserved
+ NULL // Reserved
+ ));
+
+done:
+ CoTaskMemFree(caGUID.pElems);
+ SAFE_RELEASE(FilterInfo.pGraph);
+ SAFE_RELEASE(pSpecify);
+ return hr;
+}
diff --git a/source/olepropframe.c b/source/olepropframe.c
new file mode 100644
index 0000000..6357d0c
--- /dev/null
+++ b/source/olepropframe.c
@@ -0,0 +1,313 @@
+/*
+ * Valentin Schmidt 2020
+ *
+ * Based on Wine code (https://dl.winehq.org/wine/source/) and this suggested extension/fix by
+ * Jason Winter: https://www.winehq.org/pipermail/wine-bugs/2015-July/417234.html
+ */
+#ifdef DIALOG_WITHOUT_REGISTRATION
+#include
+
+#define COBJMACROS
+#define NONAMELESSUNION
+
+#include
+#include "ole2.h"
+#include "olectl.h"
+#include "oledlg.h"
+
+#define DWL_MSGRESULT 0
+static HWND hwndFrom = NULL;
+static HWND hwndFor = NULL;
+
+typedef struct
+{
+ IPropertyPageSite IPropertyPageSite_iface;
+ LCID lcid;
+ LONG ref;
+} PropertyPageSite;
+
+static inline PropertyPageSite *impl_from_IPropertyPageSite(IPropertyPageSite *iface)
+{
+ return CONTAINING_RECORD(iface, PropertyPageSite, IPropertyPageSite_iface);
+}
+
+static INT_PTR CALLBACK property_sheet_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
+{
+ IPropertyPage *property_page = (IPropertyPage*)GetWindowLongPtrW(hwnd, DWLP_USER);
+
+ switch(msg)
+ {
+ case WM_INITDIALOG:
+ {
+ RECT rect;
+ property_page = (IPropertyPage*)((LPPROPSHEETPAGEW)lparam)->lParam;
+ GetClientRect(hwnd, &rect);
+ IPropertyPage_Activate(property_page, hwnd, &rect, TRUE);
+ IPropertyPage_Show(property_page, SW_SHOW);
+ SetWindowLongPtrW(hwnd, DWLP_USER, (LONG_PTR)property_page);
+ return FALSE;
+ }
+
+ case WM_NOTIFY:
+ switch (((NMHDR FAR *)lparam)->code)
+ {
+ case PSN_APPLY:
+ if (IPropertyPage_Apply(property_page) == NOERROR)
+ {
+ SetWindowLong(hwnd, DWL_MSGRESULT, PSNRET_NOERROR);
+ PropSheet_UnChanged(((NMHDR FAR *)lparam)->hwndFrom, hwnd); // Reset Apply button
+ }
+ else
+ SetWindowLong(hwnd, DWL_MSGRESULT, PSNRET_INVALID);
+ break;
+ case PSN_SETACTIVE:
+ hwndFrom = ((NMHDR FAR *)lparam)->hwndFrom;
+ hwndFor = hwnd;
+ break;
+ case PSN_KILLACTIVE:
+ SetWindowLong(hwnd, DWL_MSGRESULT, FALSE);
+ break;
+ }
+ return FALSE;
+
+ case WM_DESTROY:
+ IPropertyPage_Show(property_page, SW_HIDE);
+ IPropertyPage_Deactivate(property_page);
+ hwndFrom = NULL;
+ hwndFor = NULL;
+ return FALSE;
+
+ default:
+ return FALSE;
+ }
+}
+
+static HRESULT WINAPI PropertyPageSite_QueryInterface(IPropertyPageSite* iface,
+ REFIID riid, void** ppv)
+{
+ if(IsEqualGUID(&IID_IUnknown, riid)
+ || IsEqualGUID(&IID_IPropertyPageSite, riid))
+ *ppv = iface;
+ else {
+ *ppv = NULL;
+ return E_NOINTERFACE;
+ }
+ IUnknown_AddRef((IUnknown*)*ppv);
+ return S_OK;
+}
+
+static ULONG WINAPI PropertyPageSite_AddRef(IPropertyPageSite* iface)
+{
+ PropertyPageSite *this = impl_from_IPropertyPageSite(iface);
+ LONG ref = InterlockedIncrement(&this->ref);
+ return ref;
+}
+
+static ULONG WINAPI PropertyPageSite_Release(IPropertyPageSite* iface)
+{
+ PropertyPageSite *this = impl_from_IPropertyPageSite(iface);
+ LONG ref = InterlockedDecrement(&this->ref);
+ if(!ref)
+ HeapFree(GetProcessHeap(), 0, this);
+ return ref;
+}
+
+static HRESULT WINAPI PropertyPageSite_OnStatusChange(
+ IPropertyPageSite *iface, DWORD dwFlags)
+{
+ IPropertyPage *property_page;
+ if ((hwndFrom) && (hwndFor))
+ {
+ // Were they set?
+ if ((dwFlags & PROPPAGESTATUS_DIRTY) == PROPPAGESTATUS_DIRTY)
+ PropSheet_Changed(hwndFrom, hwndFor); // Enable Apply button
+
+ if ((dwFlags & PROPPAGESTATUS_VALIDATE) == PROPPAGESTATUS_VALIDATE)
+ {
+ property_page = (IPropertyPage*)GetWindowLongPtrW(hwndFor,
+ DWLP_USER);
+ if ((property_page) &&
+ (IPropertyPage_Apply(property_page) == NOERROR))
+ PropSheet_UnChanged(hwndFrom, hwndFor); // Reset Apply button
+ }
+
+ if ((dwFlags & PROPPAGESTATUS_CLEAN) == PROPPAGESTATUS_CLEAN)
+ PropSheet_UnChanged(hwndFrom, hwndFor); // Reset Apply button
+ }
+ return S_OK;
+}
+
+static HRESULT WINAPI PropertyPageSite_GetLocaleID(
+ IPropertyPageSite *iface, LCID *pLocaleID)
+{
+ PropertyPageSite *this = impl_from_IPropertyPageSite(iface);
+ *pLocaleID = this->lcid;
+ return S_OK;
+}
+
+static HRESULT WINAPI PropertyPageSite_GetPageContainer(
+ IPropertyPageSite* iface, IUnknown** ppUnk)
+{
+ return E_NOTIMPL;
+}
+
+static HRESULT WINAPI PropertyPageSite_TranslateAccelerator(
+ IPropertyPageSite* iface, MSG *pMsg)
+{
+ return E_NOTIMPL;
+}
+
+static IPropertyPageSiteVtbl PropertyPageSiteVtbl = {
+ PropertyPageSite_QueryInterface,
+ PropertyPageSite_AddRef,
+ PropertyPageSite_Release,
+ PropertyPageSite_OnStatusChange,
+ PropertyPageSite_GetLocaleID,
+ PropertyPageSite_GetPageContainer,
+ PropertyPageSite_TranslateAccelerator
+};
+
+LONG WINAPI GdiGetCharDimensions(HDC hdc, LPTEXTMETRICW lptm, LONG *height)
+{
+ SIZE sz;
+ static const WCHAR alphabet[] = {
+ 'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q',
+ 'r','s','t','u','v','w','x','y','z','A','B','C','D','E','F','G','H',
+ 'I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z',0 };
+ if (lptm && !GetTextMetricsW(hdc, lptm)) return 0;
+ if (!GetTextExtentPointW(hdc, alphabet, 52, &sz)) return 0;
+ if (height) *height = sz.cy;
+ return (sz.cx / 26 + 1) / 2;
+}
+
+/***********************************************************************
+ * Custom function by Valentin Schmidt, based on Wine's OleCreatePropertyFrame implementation
+ */
+HRESULT WINAPI OleCreatePropertyFrameDirect(
+ HWND hwndOwner,
+ LPCOLESTR lpszCaption,
+ LPUNKNOWN* ppUnk,
+ IPropertyPage * page)
+{
+ static const WCHAR comctlW[] = { 'c','o','m','c','t','l','3','2','.','d','l','l',0 };
+ PROPSHEETHEADERW property_sheet;
+ PROPSHEETPAGEW property_sheet_page;
+ struct
+ {
+ DLGTEMPLATE template;
+ WORD menu;
+ WORD class;
+ WORD title;
+ } *dialogs;
+ PropertyPageSite *property_page_site;
+ HRESULT res;
+ HMODULE hcomctl;
+ HRSRC property_sheet_dialog_find = NULL;
+ HGLOBAL property_sheet_dialog_load = NULL;
+ WCHAR *property_sheet_dialog_data = NULL;
+ HDC hdc;
+ LOGFONTW font_desc;
+ HFONT hfont;
+ LONG font_width = 4, font_height = 8;
+ hdc = GetDC(NULL);
+ hcomctl = LoadLibraryW(comctlW);
+ if (hcomctl)
+ property_sheet_dialog_find = FindResourceW(hcomctl,
+ MAKEINTRESOURCEW(1006 /*IDD_PROPSHEET*/), (LPWSTR)RT_DIALOG);
+ if (property_sheet_dialog_find)
+ property_sheet_dialog_load = LoadResource(hcomctl, property_sheet_dialog_find);
+ if (property_sheet_dialog_load)
+ property_sheet_dialog_data = LockResource(property_sheet_dialog_load);
+
+ if (property_sheet_dialog_data)
+ {
+ if (property_sheet_dialog_data[1] == 0xffff)
+ {
+ FreeLibrary(hcomctl);
+ return E_OUTOFMEMORY;
+ }
+
+ property_sheet_dialog_data += sizeof(DLGTEMPLATE) / sizeof(WCHAR);
+ /* Skip menu, class and title */
+ property_sheet_dialog_data += lstrlenW(property_sheet_dialog_data) + 1;
+ property_sheet_dialog_data += lstrlenW(property_sheet_dialog_data) + 1;
+ property_sheet_dialog_data += lstrlenW(property_sheet_dialog_data) + 1;
+
+ memset(&font_desc, 0, sizeof(LOGFONTW));
+ /* Calculate logical height */
+ font_desc.lfHeight = -MulDiv(property_sheet_dialog_data[0],
+ GetDeviceCaps(hdc, LOGPIXELSY), 72);
+ font_desc.lfCharSet = DEFAULT_CHARSET;
+ memcpy(font_desc.lfFaceName, property_sheet_dialog_data + 1,
+ sizeof(WCHAR)*(lstrlenW(property_sheet_dialog_data + 1) + 1));
+ hfont = CreateFontIndirectW(&font_desc);
+
+ if (hfont)
+ {
+ hfont = SelectObject(hdc, hfont);
+ font_width = GdiGetCharDimensions(hdc, NULL, &font_height);
+ SelectObject(hdc, hfont);
+ }
+ }
+ if (hcomctl)
+ FreeLibrary(hcomctl);
+ ReleaseDC(NULL, hdc);
+
+ memset(&property_sheet, 0, sizeof(property_sheet));
+ property_sheet.dwSize = sizeof(property_sheet);
+ if (lpszCaption)
+ {
+ property_sheet.dwFlags = PSH_PROPTITLE;
+ property_sheet.pszCaption = lpszCaption;
+ }
+
+ property_sheet.u3.phpage = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(HPROPSHEETPAGE));
+
+ dialogs = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*dialogs));
+ if (!property_sheet.u3.phpage || !dialogs)
+ {
+ HeapFree(GetProcessHeap(), 0, property_sheet.u3.phpage);
+ HeapFree(GetProcessHeap(), 0, dialogs);
+ return E_OUTOFMEMORY;
+ }
+
+ memset(&property_sheet_page, 0, sizeof(PROPSHEETPAGEW));
+ property_sheet_page.dwSize = sizeof(PROPSHEETPAGEW);
+ property_sheet_page.dwFlags = PSP_DLGINDIRECT | PSP_USETITLE;
+
+ property_sheet_page.pfnDlgProc = property_sheet_proc;
+
+ PROPPAGEINFO page_info;
+ property_page_site = HeapAlloc(GetProcessHeap(), 0, sizeof(PropertyPageSite));
+ if (!property_page_site)
+ goto done;
+
+ property_page_site->IPropertyPageSite_iface.lpVtbl = &PropertyPageSiteVtbl;
+ property_page_site->ref = 1;
+ property_page_site->lcid = 0;
+
+ res = IPropertyPage_SetPageSite(page, &property_page_site->IPropertyPageSite_iface);
+ IPropertyPageSite_Release(&property_page_site->IPropertyPageSite_iface);
+ if (FAILED(res))
+ goto done;
+
+ res = IPropertyPage_SetObjects(page, 1, ppUnk);
+ res = IPropertyPage_GetPageInfo(page, &page_info);
+ if (FAILED(res))
+ goto done;
+
+ dialogs[0].template.cx = MulDiv(page_info.size.cx, 4, font_width);
+ dialogs[0].template.cy = MulDiv(page_info.size.cy, 8, font_height);
+
+ property_sheet_page.u.pResource = &dialogs[0].template;
+ property_sheet_page.lParam = (LPARAM)page;
+ property_sheet_page.pszTitle = page_info.pszTitle;
+ property_sheet.u3.phpage[property_sheet.nPages++] = CreatePropertySheetPageW(&property_sheet_page);
+ PropertySheetW(&property_sheet);
+
+done:
+ HeapFree(GetProcessHeap(), 0, dialogs);
+ HeapFree(GetProcessHeap(), 0, property_sheet.u3.phpage);
+ return S_OK;
+}
+#endif
diff --git a/source/resource.h b/source/resource.h
new file mode 100644
index 0000000..4f9e5c4
--- /dev/null
+++ b/source/resource.h
@@ -0,0 +1,12 @@
+#ifndef IDC_STATIC
+#define IDC_STATIC (-1)
+#endif
+
+#define IDD_SPOUTCAMBOX 401
+#define IDC_FPS 402
+#define IDC_RESOLUTION 403
+#define IDC_MIRROR 404
+#define IDC_FLIP 405
+#define IDC_SWAP 406
+#define IDC_VERS 407
+#define IDS_TITLE 1015
diff --git a/source/version.h b/source/version.h
index 5e352fa..fb2043c 100644
--- a/source/version.h
+++ b/source/version.h
@@ -9,7 +9,7 @@
#define _VER_MAJORVERSION_STRING "2"
#define _VER_MINORVERSION 1
-#define _VER_MINORVERSION_STRING ".017"
+#define _VER_MINORVERSION_STRING "023"
#define _VER_BUGFIXVERSION 0
#define _VER_BUGFIXVERSION_STRING "0"