diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5c65bd9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +PTZControl/.vs +PTZControl/Release/ +PTZControl/Debug/ +PTZControl/Save/ +PTZControl/x64/ +PTZControl/PTZControl.vcxproj.filters +PTZControl/PTZControl.vcxproj.user +PTZControl/PTZControlDEU.docx +PTZControl/PTZControl.exe +*.docx +*.bak diff --git a/PTZControl/ExtensionUnit.cpp b/PTZControl/ExtensionUnit.cpp new file mode 100644 index 0000000..6800473 --- /dev/null +++ b/PTZControl/ExtensionUnit.cpp @@ -0,0 +1,665 @@ +#include "pch.h" +// #include +// #include +// #include // Including this before DShow.h avoids a bunch of C4995 warnings for unsafe +// // string functions if (and only if) strsafe.h is not included above. + +#include +#include +#define NO_DSHOW_STRSAFE // Avoid more C4995 warnings in intrin.h +#include +#include +#include + +#pragma comment(lib, "strmiids.lib") + +#include "ExtensionUnit.h" + +#define NONODE 0xFFFFFFFF + +////////////////////////////////////////////////////////////////////////// + + +void MySleep(int mSec) +{ + // TIcks per second + LARGE_INTEGER li; + QueryPerformanceFrequency(&li); + + LARGE_INTEGER liStart; + QueryPerformanceCounter(&liStart); + + LARGE_INTEGER liEnd; + liEnd.QuadPart = liStart.QuadPart + (li.QuadPart*mSec)/1000; + + LARGE_INTEGER liNow; + do + { + QueryPerformanceCounter(&liNow); + } + while (liNow.QuadPart pSysDevEnum; + HRESULT hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER, __uuidof(ICreateDevEnum), (void **)&pSysDevEnum); + if(FAILED(hr)) + return hr; + + // Obtain a class enumerator for the video input device category + CComPtr pEnumCat; + hr = pSysDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, &pEnumCat, 0); + if(hr == S_OK) + { + hr = HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND); + + // Enumerate the monikers and check if we can find a matching device + CComPtr pMoniker; + ULONG cFetched; + while(pEnumCat->Next(1, &pMoniker, &cFetched) == S_OK) + { + if(DeviceMatches(pMoniker, bstrDevicePath, wVID, wPID)) + { + hr = OpenDevice(pMoniker); + break; // We're done searching (even if the OpenDevice() call above failed) + } + pMoniker = nullptr; + } + } + + return hr; +} + +void CWebcamController::CloseDevice() +{ + m_spKsControl = nullptr; + m_spAMCameraControl = nullptr; + m_spsPropertySet = nullptr; + m_spCameraControl = nullptr; + + m_dwXUDeviceInformationNodeId = + m_dwXUVideoPipeControlNodeId = + m_dwXUTestDebugNodeId = + m_dwXUPeripheralControlNodeId = NONODE; + + // Use IAMCameraControl motion control + m_bUseLogitechMotionControl = false; + m_iMotorIntervalTimer = DEFAULT_MOTOR_INTERVAL_TIMER; + + // The PTZ Pro 2 has a mechanical pan tilt + m_bMechanicalPanTilt = false; + m_lDigitalTiltMin = m_lDigitalTiltMax = m_lDigitalPanMin = m_lDigitalPanMax = -1; +} + +HRESULT CWebcamController::IsPeripheralPropertySetSupported() +{ + if (!m_spKsControl) + return -1; + + KSP_NODE extProp{}; + extProp.Property.Set = LOGITECH_XU_PERIPHERAL_CONTROL; + extProp.Property.Id = 0; + extProp.Property.Flags = KSPROPERTY_TYPE_SETSUPPORT | KSPROPERTY_TYPE_TOPOLOGY; + extProp.NodeId = m_dwXUPeripheralControlNodeId; + extProp.Reserved = 0; + ULONG ulBytesReturned = 0; + return m_spKsControl->KsProperty((PKSPROPERTY)&extProp, sizeof(extProp), NULL, 0, &ulBytesReturned); +} + +HRESULT CWebcamController::GetProperty(LOGITECH_XU_PROPERTYSET lPropertySet,ULONG ulPropertyId, ULONG ulSize, VOID *pValue) +{ + if (!m_spKsControl) + return -1; + + ASSERT(pValue!=0 && ulSize!=0); + + KSP_NODE extprop{}; + extprop.Property.Id = ulPropertyId; + extprop.Property.Flags = KSPROPERTY_TYPE_GET | KSPROPERTY_TYPE_TOPOLOGY; + + switch(lPropertySet) + { + case XU_DEVICE_INFORMATION: + extprop.NodeId = m_dwXUDeviceInformationNodeId; + extprop.Property.Set = LOGITECH_XU_DEVICE_INFORMATION; + break; + case XU_VIDEOPIPE_CONTROL: + extprop.NodeId = m_dwXUVideoPipeControlNodeId; + extprop.Property.Set = LOGITECH_XU_VIDEOPIPE_CONTROL; + break; + case XU_TEST_DEBUG: + extprop.NodeId = m_dwXUTestDebugNodeId; + extprop.Property.Set = LOGITECH_XU_TEST_DEBUG; + break; + case XU_PERIPHERAL_CONTROL: + extprop.NodeId = m_dwXUPeripheralControlNodeId; + extprop.Property.Set = LOGITECH_XU_PERIPHERAL_CONTROL; + break; + } + + HRESULT hr = m_spKsControl->KsProperty( + (PKSPROPERTY)&extprop, + sizeof(extprop), + pValue, + ulSize, + &ulSize + ); + + return hr; +} + + +HRESULT CWebcamController::SetProperty(LOGITECH_XU_PROPERTYSET lPropertySet,ULONG ulPropertyId, ULONG ulSize, VOID *pValue) +{ + if (!m_spKsControl) + return -1; + + ASSERT(pValue != 0 && ulSize != 0); + + KSP_NODE extprop{}; + extprop.Property.Id = ulPropertyId; + extprop.Property.Flags = KSPROPERTY_TYPE_SET | KSPROPERTY_TYPE_TOPOLOGY; + + switch(lPropertySet) + { + case XU_DEVICE_INFORMATION: + extprop.NodeId = m_dwXUDeviceInformationNodeId; + extprop.Property.Set = LOGITECH_XU_DEVICE_INFORMATION; + break; + case XU_VIDEOPIPE_CONTROL: + extprop.NodeId = m_dwXUVideoPipeControlNodeId; + extprop.Property.Set = LOGITECH_XU_VIDEOPIPE_CONTROL; + break; + case XU_TEST_DEBUG: + extprop.NodeId = m_dwXUTestDebugNodeId; + extprop.Property.Set = LOGITECH_XU_TEST_DEBUG; + break; + case XU_PERIPHERAL_CONTROL: + extprop.NodeId = m_dwXUPeripheralControlNodeId; + extprop.Property.Set = LOGITECH_XU_PERIPHERAL_CONTROL; + break; + } + + ULONG ulBytesReturned; + HRESULT hr = m_spKsControl->KsProperty( + (PKSPROPERTY)&extprop, + sizeof(extprop), + pValue, + ulSize, + &ulBytesReturned + ); + + + return hr; +} + +/* +* Checks whether the device represented by the given IMoniker matches the given device path +* or the given VID/PID. +* +* If the device path is NULL no device path matching is performed. +* A VID or PID of 0 is considered to always match. If the VID and PID are both 0 no VID/PID +* matching is performed. +*/ +bool CWebcamController::DeviceMatches(CComPtr pMoniker, BSTR devicePath, DWORD wVID, DWORD wPID) +{ + + bool match = false; + CComPtr pPropBag; + CComVariant varPath; + + // Open the device properties + HRESULT hr = pMoniker->BindToStorage(NULL, NULL, __uuidof(IPropertyBag), (void **)&pPropBag); + if(FAILED(hr)) + goto done; + + // Retrieve the device path + hr = pPropBag->Read(L"DevicePath", &varPath, 0); + if(FAILED(hr) || varPath.bstrVal == NULL) + goto done; + + // Return true if the device path matches + if(devicePath!=NULL) + { + // Parse the device path for vid pid + m_dwVid = m_dwPid = 0; + if (FAILED(varPath.ChangeType(VT_BSTR))) + return false; + + if (_wcsicmp(varPath.bstrVal, devicePath) == 0) + { + // Lowercase + _wcslwr_s(varPath.bstrVal, SysStringLen(varPath.bstrVal) + 1); + ParseDevicePath(varPath.bstrVal, m_dwVid, m_dwPid); + + match = true; + goto done; + } + } + + // Return true if the USB information matches + if(wVID || wPID) + { + // Parse the device path (Convert it to lower-case first for safer parsing) + _wcslwr_s(varPath.bstrVal, SysStringLen(varPath.bstrVal) + 1); + DWORD vid = 0, pid = 0; + if(ParseDevicePath(varPath.bstrVal, vid, pid)) + { + match = + (wVID == 0 || vid == wVID) && + (wPID == 0 || pid == wPID); + } + } + +done: + VariantClear(&varPath); + return match; +} + +HRESULT CWebcamController::OpenDevice(CComPtr pMoniker) +{ + CComPtr pKsControl; + + // Get a pointer to the IKsControl interface + HRESULT hr = pMoniker->BindToObject(NULL, NULL, __uuidof(IKsControl), (void **)&pKsControl); + if(FAILED(hr)) + return hr; + + // Find the H.264 XU node + hr = InitializeXUNodesArray(pKsControl); + if(SUCCEEDED(hr)) + { + // save the pointer, we succeeded + m_spKsControl = pKsControl; + + m_spAMCameraControl = pKsControl; + m_spsPropertySet = pKsControl; + m_spCameraControl = pKsControl; // not supported + } + + if (m_spAMCameraControl!=nullptr) + { + long lValue = 0, lMin = 0, lMax = 0, lSteppingSize = 0, lDefaults = 0, lFlags = 0; + HRESULT hResult = m_spAMCameraControl->Get(KSPROPERTY_CAMERACONTROL_PAN_RELATIVE, &lValue, &lFlags); + m_bMechanicalPanTilt = SUCCEEDED(hr); + + { + for (int i=0; i<=19; ++i) + { + hResult = m_spAMCameraControl->Get(i, &lValue, &lFlags); + if (SUCCEEDED(hr)) + TRACE(__FUNCTION__ " m_spAMCameraControl-%d val=%d, flag=%d\n",i, lValue, lFlags); + } + + } + + // The PTZ Pro 2 has a mechanical pan tilt + ASSERT(m_bMechanicalPanTilt); + +// { +// HRESULT hResult = GetProperty( KSPROPERTY_CAMERACONTROL_PANTILT_RELATIVE, &lValue, &lFlags); +// } + + hResult = m_spAMCameraControl->GetRange(CameraControl_Pan, &lMin, &lMax, &lSteppingSize, &lDefaults, &lFlags); + if (S_OK == hResult) + { + m_lDigitalPanMin = lMin; + m_lDigitalPanMax = lMax; + } + lMin = lMax = lSteppingSize = lDefaults = lFlags = 0; + hResult = m_spAMCameraControl->GetRange(CameraControl_Tilt, &lMin, &lMax, &lSteppingSize, &lDefaults, &lFlags); + if (S_OK == hResult) + { + m_lDigitalTiltMin = lMin; + m_lDigitalTiltMax = lMax; + } + } + + + return hr; +} + +bool CWebcamController::ParseDevicePath(const wchar_t *devicePath, DWORD &vid, DWORD &pid) +{ + if(swscanf_s(devicePath, L"\\\\?\\usb#vid_%04x&pid_%04x", &vid, &pid) != 2) + return false; + return true; +} + +void CWebcamController::GetVidPid(DWORD& vid, DWORD& pid) +{ + vid = m_dwVid; + pid = m_dwPid; +} + +void CWebcamController::GotoHome() +{ + // Zoom to Home + { + DWORD dwValue = 0; + SetProperty(XU_VIDEOPIPE_CONTROL, XU_VIDEO_FW_ZOOM_CONTROL, sizeof(dwValue), &dwValue); + } + // Zoom to Home + // Home = No Action + // 1 ? + // 2 ? + // Goto Home = 3 + // 8 Presets + // Preset 1-8 = 4-11, + // Goto Preset 1-8 = 12-19 + // Test 22 + { + DWORD dwValue(3); + SetProperty(XU_PERIPHERAL_CONTROL, XU_PERIPHERALCONTROL_PANTILT_MODE_CONTROL, sizeof(DWORD), &dwValue); + } + return; +} + +void CWebcamController::SavePreset(int iNum) +{ + if (iNum<0 || iNum>=NUM_PRESETS) + return; + + // Zoom to Home + // Home = No Action + // 1 ? + // 2 ? + // Goto Home = 3 + // 8 Presets + // Preset 1-8 = 4-11, + // Goto Preset 1-8 = 12-19 + // Test 22 + DWORD dwValue(iNum+4); + SetProperty(XU_PERIPHERAL_CONTROL, XU_PERIPHERALCONTROL_PANTILT_MODE_CONTROL, sizeof(DWORD), &dwValue); +} + +void CWebcamController::GotoPreset(int iNum) +{ + if (iNum<0 || iNum>=NUM_PRESETS) + return; + + // Zoom to Home + // Home = No Action + // 1 ? + // 2 ? + // Goto Home = 3 + // 8 Presets + // Preset 1-8 = 4-11, + // Goto Preset 1-8 = 12-19 + // Test 22 + DWORD dwValue(iNum + 12); + SetProperty(XU_PERIPHERAL_CONTROL, XU_PERIPHERALCONTROL_PANTILT_MODE_CONTROL, sizeof(DWORD), &dwValue); +} + +int CWebcamController::GetCurrentZoom() +{ + if (!m_spAMCameraControl) + return -1; + + long oldZoom = 0, oldFlags = 0; + oldFlags = CameraControl_Flags_Manual; + m_spAMCameraControl->Get(CameraControl_Zoom, &oldZoom, &oldFlags); + return oldZoom; +} + +int CWebcamController::Zoom(int direction) +{ + if (!m_spAMCameraControl) + return -1; + + long lZoomMin = 0, lZoomMax = 0, lZoomDefault = 0, lZoomStep = 0, lFlags = CameraControl_Flags_Manual; + m_spAMCameraControl->GetRange(CameraControl_Zoom, &lZoomMin, &lZoomMax, &lZoomStep, &lZoomDefault, &lFlags); + + long lOldZoom = GetCurrentZoom(); + if (lOldZoomlZoomMax) + lOldZoom = lZoomDefault; + + long lNewZoom = lOldZoom; + + // Devide in 150 parts + int iStep = (lZoomMax-lZoomMin)/150; + if (iStep==0) + iStep = 1; + + // calculate new zoom + lNewZoom = lOldZoom + lZoomStep*direction*iStep; // Just get 100 steps + + m_spAMCameraControl->Set(CameraControl_Zoom, lNewZoom, CameraControl_Flags_Manual); + return lNewZoom; +} + +void CWebcamController::Tilt(int yDirection) +{ + if (m_spAMCameraControl) + m_spAMCameraControl->Set(KSPROPERTY_CAMERACONTROL_TILT_RELATIVE, yDirection!=0 ? (yDirection < 0 ? -1 : 1) : 0, 0); +} + +void CWebcamController::MoveTilt(int yDirection) +{ + if (m_bUseLogitechMotionControl && m_dwXUPeripheralControlNodeId!=NONODE) + { + DWORD dwValue = MAKELONG(MAKEWORD(0, 0), MAKEWORD(0, yDirection < 0 ? 1 : -1)); + SetProperty(XU_PERIPHERAL_CONTROL, XU_PERIPHERALCONTROL_PANTILT_RELATIVE_CONTROL, sizeof(DWORD), &dwValue); + } + else + { + if (!m_spAMCameraControl) + return; + + if (m_bMechanicalPanTilt) + { + if (yDirection != 0) + { + Tilt(yDirection); + MySleep(m_iMotorIntervalTimer); + Tilt(0); + } + } + else + { + long lValue(0), lFlags(0); + if (yDirection != 0) + { + HRESULT hResult = m_spAMCameraControl->Get(CameraControl_Tilt, &lValue, &lFlags); + if (S_OK == hResult) + { + lValue += yDirection; + if (yDirection > 0 && lValue > m_lDigitalTiltMax) + lValue = m_lDigitalTiltMax; + if (yDirection < 0 && lValue < m_lDigitalTiltMin) + lValue = m_lDigitalTiltMin; + hResult = m_spAMCameraControl->Set(CameraControl_Tilt, lValue, lFlags); + } + } + } + } + return; +} + + +void CWebcamController::Pan(int xDirection) +{ + if (m_spAMCameraControl) + m_spAMCameraControl->Set(KSPROPERTY_CAMERACONTROL_PAN_RELATIVE, xDirection!=0 ? (xDirection < 0 ? -1 : 1) : 0, 0); +} + +void CWebcamController::MovePan(int xDirection) +{ + if (m_bUseLogitechMotionControl) + { + DWORD dwValue = MAKELONG(MAKEWORD(0, xDirection), MAKEWORD(0, 0)); + SetProperty(XU_PERIPHERAL_CONTROL, XU_PERIPHERALCONTROL_PANTILT_RELATIVE_CONTROL, sizeof(DWORD), &dwValue); + } + else + { + if (!m_spAMCameraControl) + return; + + if (m_bMechanicalPanTilt) + { + if (xDirection != 0) + { + Pan(xDirection); + MySleep(m_iMotorIntervalTimer); + Pan(0); + } + } + else + { + long lValue(0), lFlags(0); + if (xDirection != 0) + { + HRESULT hResult = m_spAMCameraControl->Get(CameraControl_Pan, &lValue, &lFlags); + if (S_OK == hResult) + { + lValue += xDirection; + if (xDirection > 0 && lValue > m_lDigitalPanMax) + lValue = m_lDigitalPanMax; + if (xDirection < 0 && lValue < m_lDigitalPanMin) + lValue = m_lDigitalPanMin; + hResult = m_spAMCameraControl->Set(CameraControl_Pan, lValue, lFlags); + } + } + } + } + return; +} + + +/* +* Tries to locate the node that carries H.264 XU extension and saves its ID. +*/ +HRESULT CWebcamController::InitializeXUNodesArray(CComPtr pKsControl) +{ + // Get the IKsTopologyInfo interface + CComQIPtr pKsTopologyInfo = pKsControl; + if (!pKsTopologyInfo) + return E_NOINTERFACE; + + // Retrieve the number of nodes in the filter + DWORD dwNumNodes = 0; + HRESULT hr = pKsTopologyInfo->get_NumNodes(&dwNumNodes); + if(FAILED(hr)) + return hr; + + // Go through all extension unit nodes and try to find the required XU node + hr = E_FAIL; + std::set setGuids; + for(unsigned int nodeId = 0; nodeId < dwNumNodes; nodeId++) + { + GUID guidNodeType; + hr = pKsTopologyInfo->get_NodeType(nodeId, &guidNodeType); + if(FAILED(hr)) + continue; + + // All Node types we have +// { 941C7AC0 - C559 - 11D0 - 8A2B - 00A0C9255AC1 } KSNODETYPE_DEV_SPECIFIC +// { DFF229E1 - F70F - 11D0 - B917 - 00A0C9223196 } KSNODETYPE_VIDEO_STREAMING +// { DFF229E5 - F70F - 11D0 - B917 - 00A0C9223196 } KSNODETYPE_VIDEO_PROCESSING +// { DFF229E6 - F70F - 11D0 - B917 - 00A0C9223196 } KSNODETYPE_VIDEO_CAMERA_TERMINAL + { + wchar_t szText[100]; + StringFromGUID2(guidNodeType, szText, _countof(szText)); + setGuids.emplace(szText); + } + + + if(!IsEqualGUID(guidNodeType, KSNODETYPE_DEV_SPECIFIC)) + continue; + + if(IsExtensionUnitSupported(pKsControl,LOGITECH_XU_DEVICE_INFORMATION,nodeId)) + { + m_dwXUDeviceInformationNodeId = nodeId; + } + else if(IsExtensionUnitSupported(pKsControl,LOGITECH_XU_VIDEOPIPE_CONTROL,nodeId)) + { + m_dwXUVideoPipeControlNodeId = nodeId; + } + else if(IsExtensionUnitSupported(pKsControl,LOGITECH_XU_TEST_DEBUG,nodeId)) + { + m_dwXUTestDebugNodeId = nodeId; + } + else if(IsExtensionUnitSupported(pKsControl,LOGITECH_XU_PERIPHERAL_CONTROL,nodeId)) + { + m_dwXUPeripheralControlNodeId = nodeId; + } +// else if (IsExtensionUnitSupported(pKsControl, PROPSETID_VIDCAP_CAMERACONTROL, nodeId)) +// { +// // DWORD dwNodeId = nodeId; +// } + } + for (const auto &str : setGuids) + TRACE(__FUNCTION__ " - %ls\n", str.GetString()); + return hr; +} + +bool CWebcamController::IsExtensionUnitSupported(CComPtr pKsControl,const GUID& guidExtension,unsigned int nodeId) +{ + KSP_NODE extProp{}; + extProp.Property.Set = guidExtension; + extProp.Property.Id = 0; + extProp.Property.Flags = KSPROPERTY_TYPE_SETSUPPORT | KSPROPERTY_TYPE_TOPOLOGY; + extProp.NodeId = nodeId; + extProp.Reserved = 0; + ULONG ulBytesReturned = 0; + HRESULT hr = pKsControl->KsProperty((PKSPROPERTY)&extProp, sizeof(extProp), NULL, 0, &ulBytesReturned); + return SUCCEEDED(hr); +} + + +void CWebcamController::ListDevices(CStringArray &aDevices) +{ + aDevices.RemoveAll(); + + // Get a device list + CComPtr pSysDevEnum; + HRESULT hr; + hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER, IID_ICreateDevEnum, (void**)&pSysDevEnum); + if (SUCCEEDED(hr)) + { + //create a device class enumerator + CComPtr pIEnumMoniker; + HRESULT hResult = pSysDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, &pIEnumMoniker, 0); + if (SUCCEEDED(hr)) + { + ULONG pFetched = NULL; + CComPtr pImoniker; + while (S_OK == pIEnumMoniker->Next(1, &pImoniker, &pFetched)) + { + CComPtr pPropBag; + hResult = pImoniker->BindToStorage(0, 0, IID_IPropertyBag, (void**)&pPropBag); + if (SUCCEEDED(hResult) && pPropBag!=nullptr) + { + CComVariant varCameraName, varDevicePath; + pPropBag->Read(L"FriendlyName", &varCameraName, 0); + pPropBag->Read(L"DevicePath", &varDevicePath, 0); + + if (SUCCEEDED(varCameraName.ChangeType(VT_BSTR)) && + SUCCEEDED(varDevicePath.ChangeType(VT_BSTR))) + { + CString strCameraName(varCameraName.bstrVal), strDevicePath(varDevicePath.bstrVal); + aDevices.Add(strCameraName + _T('\t') + strDevicePath); + } + } + + // Set to null + pImoniker = nullptr; + } + } + } +} diff --git a/PTZControl/ExtensionUnit.h b/PTZControl/ExtensionUnit.h new file mode 100644 index 0000000..5e0917b --- /dev/null +++ b/PTZControl/ExtensionUnit.h @@ -0,0 +1,75 @@ +#pragma once +#include +#include // For IKsControl +#include // For IKsNodeControl + +#include "ExtensionUnitDefines.h" + + +#define DEFAULT_MOTOR_INTERVAL_TIMER 70 + +/** +* CWebcamExtensionUnit encapsulates the USB extension unit capability of the device. +* (see https://msdn.microsoft.com/en-us/library/windows/hardware/ff568656(v=vs.85).aspx). +*/ + +class CWebcamController +{ +public: + CWebcamController(void); + ~CWebcamController(void); + + HRESULT OpenDevice(BSTR bstrDevicePath, DWORD wVID, DWORD wPID); + void CloseDevice(); + HRESULT IsPeripheralPropertySetSupported(); + HRESULT GetProperty(LOGITECH_XU_PROPERTYSET lPropertySet, ULONG ulPropertyId, ULONG ulSize, VOID* pValue); + HRESULT SetProperty(LOGITECH_XU_PROPERTYSET lPropertySet, ULONG ulPropertyId, ULONG ulSize, VOID* pValue); + void GetVidPid(DWORD& vid, DWORD& pid); + + int GetCurrentZoom(); + int Zoom(int direction); + void MoveTilt(int yDirection); + void MovePan(int xDirection); + void Tilt(int yDirection); + void Pan(int xDirection); + + void GotoHome(); + void SavePreset(int iNum); + void GotoPreset(int iNum); + + static void ListDevices(CStringArray &aDevices); + static const int NUM_PRESETS = 8; + + bool UseLogitechMotionControl() const { return m_bUseLogitechMotionControl; } + void UseLogitechMotionControl(bool val) { m_bUseLogitechMotionControl = val; } + + int GetMotorIntervalTimer() const { return m_iMotorIntervalTimer; } + void SetMotorIntervalTimer(int val) { m_iMotorIntervalTimer = val; } + +private: + bool DeviceMatches(CComPtr pMoniker, BSTR devicePath, DWORD wVID, DWORD wPID); + HRESULT OpenDevice(CComPtr pMoniker); + bool ParseDevicePath(const wchar_t* devicePath, DWORD& vid, DWORD& pid); + HRESULT InitializeXUNodesArray(CComPtr pKsControl); + bool IsExtensionUnitSupported(CComPtr pKsControl, const GUID& guidExtension, unsigned int nodeId); + +private: + CComPtr m_spKsControl; + CComQIPtr m_spAMCameraControl; + CComQIPtr m_spsPropertySet; + CComQIPtr m_spCameraControl; + + DWORD m_dwXUDeviceInformationNodeId; + DWORD m_dwXUVideoPipeControlNodeId; + DWORD m_dwXUTestDebugNodeId; + DWORD m_dwXUPeripheralControlNodeId; + DWORD m_dwVid; + DWORD m_dwPid; + + bool m_bMechanicalPanTilt; + long m_lDigitalTiltMin, m_lDigitalTiltMax, + m_lDigitalPanMin, m_lDigitalPanMax; + + bool m_bUseLogitechMotionControl; + int m_iMotorIntervalTimer; +}; diff --git a/PTZControl/ExtensionUnitDefines.h b/PTZControl/ExtensionUnitDefines.h new file mode 100644 index 0000000..b1c8d78 --- /dev/null +++ b/PTZControl/ExtensionUnitDefines.h @@ -0,0 +1,120 @@ +#pragma once + +DEFINE_GUID(LOGITECH_XU_DEVICE_INFORMATION, 0x69678EE4, 0x410F, 0x40DB, 0xA8, 0x50, 0x74, 0x20, 0xD7, 0xD8, 0x24, 0x0E); +DEFINE_GUID(LOGITECH_XU_VIDEOPIPE_CONTROL, 0x49E40215, 0xF434, 0x47FE, 0xB1, 0x58, 0x0E, 0x88, 0x50, 0x23, 0xE5, 0x1B); +DEFINE_GUID(LOGITECH_XU_TEST_DEBUG, 0x1F5D4CA9, 0xDE11, 0x4487, 0x84, 0x0D, 0x50, 0x93, 0x3C, 0x8E, 0xC8, 0xD1); +DEFINE_GUID(LOGITECH_XU_PERIPHERAL_CONTROL, 0xFFE52D21, 0x8030, 0x4E2C, 0x82, 0xD9, 0xF5, 0x87, 0xD0, 0x05, 0x40, 0xBD); + +enum //LOGITECH_XU_DEVICE_INFORMATION +{ + XU_DEVICE_INFORMATION_UNDEFINED_CONTROL = 0x00, + XU_FIRMWARE_VERSION_CONTROL = 0x01, + XU_FIRMWARE_CRC_CONTROL = 0x02, + XU_EEPROM_VERSION_CONTROL = 0x03, + XU_SENSOR_INFORMATION_CONTROL = 0x04, + XU_PROCESSOR_INFORMATION_CONTROL = 0x05, + XU_USB_INFORMATION_CONTROL = 0x06, + XU_Reserved1 = 0x07, + XU_Reserved2 = 0x08, + XU_LENS_FOV_CONTROL = 0x09, + XU_SENSOR_DIMENSION_CONTROL = 0x0A, + XU_EXTENDED_FIRMWARE_VERSION_CONTROL = 0x0B, +}; + +enum //LOGITECH_XU_VIDEOPIPE_CONTROL +{ + XU_VIDEO_UNDEFINED_CONTROL = 0x00, + XU_VIDEO_COLOR_BOOST_CONTROL = 0x01, + XU_VIDEO_NATIVE_MODE_FORCED_CONTROL = 0x02, + XU_VIDEO_NATIVE_MODE_AUTO_CONTROL = 0x03, + XU_VIDEO_RIGHTLIGHT_MODE_CONTROL = 0x04, + XU_VIDEO_RIGHTLIGHT_ZOI_CONTROL = 0x05, + XU_VIDEO_FW_ZOOM_CONTROL = 0x06, + XU_VIDEO_DUAL_ISO_ENABLE_CONTROL = 0x07, + XU_VIDEO_SENSOR_CROPPING_DIMENSION_CONTROL = 0x08, + XU_VIDEO_MJPEG_RESYNC_MARKER_CONTROL = 0x09, + XU_VIDEO_ADVANCE_DIGITAL_ZOOM_CONTROL = 0x0A, + XU_VIDEO_MJPEG_COMPRESS_RATIO_CONTROL = 0x0B, + XU_VIDEO_HDR_CONTROL = 0x0C, +}; + +enum //LOGITECH_XU_TEST_DEBUG +{ + XU_TESTDEBUG_UNDEFINED_CONTROL = 0x00, + XU_TEST_REGISTER_ADDRESS_CONTROL = 0x01, + XU_TEST_REGISTER_ACCESS_CONTROL = 0x02, + XU_TEST_EEPROM_ADDRESS_CONTROL = 0x03, + XU_TEST_EEPROM_ACCESS_CONTROL = 0x04, + XU_TEST_SENSOR_ADDRESS_CONTROL = 0x05, + XU_TEST_SENSOR_ACCESS_CONTROL = 0x06, + XU_PERIPHERAL_MODE_CONTROL = 0x07, + XU_PERIPHERAL_OP_CONTROL = 0x08, + XU_PERIPHERAL_ACCESS_CONTROL = 0x09, + XU_TEST_TDE_MODE_CONTROL = 0x0A, + XU_TEST_GAIN_ACCESS_CONTROL = 0x0B, + XU_TEST_LOW_LIGHT_PRIORITY_CONTROL = 0x0C, + XU_TEST_COLOR_PROCESSING_DISABLE_CONTROL = 0x0D, + XU_TEST_PIXEL_DEFECT_CORRECTION_CONTROL = 0x0E, + XU_TEST_LENS_SHADING_COMPENSATION_CONTROL = 0x0F, + XU_TEST_GAMMA_CONTROL = 0x10, + XU_TEST_INTEGRATION_TIME_CONTROL = 0x11, + XU_TEST_RAW_DATA_BITS_PER_PIXEL_CONTROL = 0x12, + XU_TEST_ISP_ADDRESS_CONTROL = 0x13, + XU_TEST_ISP_ACCESS_CONTROL = 0x14, + XU_PERIPHERAL_ACCESS_EXT_CONTROL = 0x15, + XU_H264_FRAME_NO_CONTROL = 0x16, + +}; + +// Seams that only 2 modes are defined for the PTZ Pro 2 +// XU_PERIPHERALCONTROL_PANTILT_RELATIVE_CONTROL = pan tilt +// XU_PERIPHERALCONTROL_PANTILT_MODE_CONTROL = Preset and recall position +enum //LOGITECH_XU_PERIPHERAL_CONTROL +{ + XU_PERIPHERAL_UNDEFINED_CONTROL = 0x00, + XU_PERIPHERALCONTROL_PANTILT_RELATIVE_CONTROL = 0x01, // pan tilt + XU_PERIPHERALCONTROL_PANTILT_MODE_CONTROL = 0x02, // Preset and recall position + XU_PERIPHERALCONTROL_MAXIMUM_RESOLUTION_SUPPORT_FOR_PANTILT_CONTROL = 0x03, + XU_PERIPHERALCONTROL_AF_MOTORCONTROL = 0x04, + XU_PERIPHERALCONTROL_AF_BLOB_CONTROL = 0x05, + XU_PERIPHERALCONTROL_AF_VCM_PARAMETERS = 0x06, + XU_PERIPHERALCONTROL_AF_STATUS = 0x07, + XU_PERIPHERALCONTROL_AF_THRESHOLDS = 0x08, + XU_PERIPHERALCONTROL_LED = 0x09, + XU_PERIPHERAL_CONTROL_PERIPHERAL_STATUS = 0x0A, + XU_PERIPHERAL_CONTROL_SPEAKER_VOLUME = 0x0B, + XU_PERIPHERAL_CONTROL_DEVICE_CODEC_STATUS = 0x0C, + XU_PERIPHERAL_CONTROL_SPEAKER = 0x0D, + XU_PERIPHERAL_CONTROL_MODE = 0x0E, + XU_AUDIO_LIBRARY_MODE_CONTROL = 0x0F, + XU_PERIPHERAL_MOTOR_STEPS_CONTROL = 0x10, + +}; + + +typedef enum +{ + XU_DEVICE_INFORMATION, + XU_VIDEOPIPE_CONTROL, + XU_TEST_DEBUG, + XU_PERIPHERAL_CONTROL, +} LOGITECH_XU_PROPERTYSET; + +/** +* Preset enumerations +*/ +enum +{ + RESET_NOACTION = 0, + SET_PRESET = 1, + GOTO_PRESET = 2, + GOTOHOME = 3 +}; + +// enum +// { +// RESET_NOACTION = 0, +// RESET_PAN = 1, +// RESET_TILT = 2, +// }; + diff --git a/PTZControl/PTZControl.aps b/PTZControl/PTZControl.aps new file mode 100644 index 0000000..d5225d5 Binary files /dev/null and b/PTZControl/PTZControl.aps differ diff --git a/PTZControl/PTZControl.cpp b/PTZControl/PTZControl.cpp new file mode 100644 index 0000000..fa031f4 --- /dev/null +++ b/PTZControl/PTZControl.cpp @@ -0,0 +1,169 @@ + +// PTZControl.cpp : Defines the class behaviors for the application. +// + +#include "pch.h" +#include "framework.h" +#include "PTZControl.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#endif + +///////////////////////////////////////////////////////////////////////////// +// CAgvipCommandLineInfo +// The program must be started with the parameters /conn: +// and /user:. For the Debug mode special Options are prepared. + +class CPTZControlCommandLineInfo : public CCommandLineInfo +{ +public: + // Construction + CPTZControlCommandLineInfo() + : m_bNoReset(false) + , m_bNoGuard(false) + , m_bShowDevices(false) + { + + } + + // Overwritten virtual + virtual void ParseParam(const TCHAR* pszParam,BOOL bFlag,BOOL bLast); +#ifdef _UNICODE + virtual void ParseParam(const char* pszParam, BOOL bFlag, BOOL bLast); +#endif + +public: + // Data + CString m_strDevName; // Device name from the command line to search for + bool m_bNoReset; // No Reset of web cam + bool m_bNoGuard; // Prevent a guard thread + bool m_bShowDevices; // SHow message box with devicenames on open. + + // Currently not used (may be used if we ant yes/no/undefined) + enum class Mode + { + False = 0, + True = 1, + Undefined = -1, + }; +}; + +#ifdef _UNICODE +void CPTZControlCommandLineInfo::ParseParam(const TCHAR* pszParam,BOOL bFlag,BOOL bLast) +{ + ParseParam(CT2CA(pszParam),bFlag,bLast); +} +#endif + +void CPTZControlCommandLineInfo::ParseParam(const char* pszParam,BOOL bFlag,BOOL bLast) +{ + if (bFlag) + { + if (_strnicmp(pszParam,"device:",7)==0) + { + // Get address set name + pszParam += 7; + m_strDevName = pszParam; + ::PathUnquoteSpaces(CStrBuf(m_strDevName,0)); + } + else if (_stricmp(pszParam, "noreset")==0) + { + m_bNoReset = true; + } + else if (_stricmp(pszParam, "noguard") == 0) + { + m_bNoGuard = true; + } + else if (_stricmp(pszParam, "showdevices") == 0) + { + m_bShowDevices = true; + } + else + ParseParamFlag(pszParam); + } + else + ParseParamNotFlag(pszParam); + + // Standard implementation + ParseLast(bLast); +} + + +////////////////////////////////////////////////////////////////////////// +// CPTZControlApp + +BEGIN_MESSAGE_MAP(CPTZControlApp, CWinApp) + ON_COMMAND(ID_HELP, &CWinApp::OnHelp) +END_MESSAGE_MAP() + +////////////////////////////////////////////////////////////////////////// +// CPTZControlApp construction + +CPTZControlApp::CPTZControlApp() + : m_bNoReset(false) + , m_bNoGuard(false) + , m_bShowDevices(false) +{ +} + + +// The one and only CPTZControlApp object + +CPTZControlApp theApp; + + +////////////////////////////////////////////////////////////////////////// +// CPTZControlApp initialization + +BOOL CPTZControlApp::InitInstance() +{ + CWinApp::InitInstance(); + + // Activate "Windows Native" visual manager for enabling themes in MFC controls + CMFCVisualManager::SetDefaultManager(RUNTIME_CLASS(CMFCVisualManagerWindows)); + + // Taken from Logitech + // PTZDemo\ConferenceCamPTZDemoDlg.cpp + CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); + +//-------------Registry------------------------------------------------- + + SetRegistryKey(_T("MRi-Software")); + +//-------------Commandline parsing-------------------------------------- + +// Parse command line for standard shell commands, DDE, file open + CPTZControlCommandLineInfo cmdInfo; + ParseCommandLine(cmdInfo); + + m_strDevName = cmdInfo.m_strDevName; + + // Registry is overruled command line + m_bNoReset = GetProfileInt(REG_OPTIONS,REG_NORESET,FALSE)!=0 || cmdInfo.m_bNoReset; + m_bNoGuard = GetProfileInt(REG_OPTIONS,REG_NOGUARD,FALSE)!=0 || cmdInfo.m_bNoGuard; + m_bShowDevices = cmdInfo.m_bShowDevices; + +//-------------Main ---------------------------------------------------- + + // Create the Dialog + m_pDlg = new CPTZControlDlg(); + if (m_pDlg->Create(CPTZControlDlg::IDD)) + m_pMainWnd = m_pDlg; + else + return FALSE; + + // Succeeded + return TRUE; +} + +////////////////////////////////////////////////////////////////////////// + +int CPTZControlApp::ExitInstance() +{ +#if !defined(_AFXDLL) && !defined(_AFX_NO_MFC_CONTROLS_IN_DIALOGS) + ControlBarCleanUp(); +#endif + + return __super::ExitInstance(); +} diff --git a/PTZControl/PTZControl.h b/PTZControl/PTZControl.h new file mode 100644 index 0000000..595c698 --- /dev/null +++ b/PTZControl/PTZControl.h @@ -0,0 +1,76 @@ + +// PTZControl.h : main header file for the PROJECT_NAME application +// +#pragma once + +#include "PTZControlDlg.h" + +#ifndef __AFXWIN_H__ + #error "include 'pch.h' before including this file for PCH" +#endif + +#include "resource.h" // main symbols + +////////////////////////////////////////////////////////////////////////// + +#define REG_WINDOW _T("Window") +#define REG_WINDOW_POSX _T("X") +#define REG_WINDOW_POSY _T("Y") +#define REG_TOOLTIP _T("Tooltip%d") + +#define REG_DEVICE _T("Device") +#define REG_USELOGOTECHMOTIONCONTROL _T("LogitechMotionControl") +#define REG_MOTORINTERVALTIMER _T("MotorIntervalTimer") +#define REG_DEVICENAME _T("DeviceName") + +#define REG_OPTIONS _T("Options") +#define REG_NORESET _T("NoReset") +#define REG_NOGUARD _T("NoGuard") + +#define DEFAULT_DEVICE_NAME_1 _T("PTZ Pro 2") +#define DEFAULT_DEVICE_NAME_2 _T("Logi Rally") + +#define TIMER_FOCUS_CHECK 4711 +#define TIMER_AUTO_REPEAT 4712 +#define TIMER_CLEAR_MEMORY 4713 + +#define FOCUS_CHECK_DELAY 250 // After 250msec we move the focus away from a button. +#define AUTO_REPEAT_DELAY 50 // Autorepeat is on the fastest possible delay of 50msec +#define AUTO_REPEAT_INITIAL_DELAY 500 // after 1/2 second we start autorepeat +#define CLEAR_MEMORY_DELAY 5000 // After 5 seconds clear the memory + +#define COLOR_GREEN RGB(0,240,0) +#define COLOR_RED RGB(240,0,0) +#define COLOR_ORANGE RGB(255,140,0) + +////////////////////////////////////////////////////////////////////////// +// CPTZControlApp: +// See PTZControl.cpp for the implementation of this class +// + +class CPTZControlApp : public CWinApp +{ +public: + CPTZControlApp(); + + // Overrides +public: + virtual BOOL InitInstance(); + +// Implementation + + DECLARE_MESSAGE_MAP() + virtual int ExitInstance(); + +public: + // command line flags + CString m_strDevName; // Device name from the command line to search for + bool m_bNoReset; // No Reset of web cam + bool m_bNoGuard; // Prevent a guard thread + bool m_bShowDevices; + +private: + CPTZControlDlg* m_pDlg; +}; + +extern CPTZControlApp theApp; diff --git a/PTZControl/PTZControl.rc b/PTZControl/PTZControl.rc new file mode 100644 index 0000000..d217c10 Binary files /dev/null and b/PTZControl/PTZControl.rc differ diff --git a/PTZControl/PTZControl.sln b/PTZControl/PTZControl.sln new file mode 100644 index 0000000..750a001 --- /dev/null +++ b/PTZControl/PTZControl.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30804.86 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PTZControl", "PTZControl.vcxproj", "{2E2437DA-35E2-4D0D-B1DF-26E1DFEE1FB9}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {2E2437DA-35E2-4D0D-B1DF-26E1DFEE1FB9}.Debug|x64.ActiveCfg = Debug|x64 + {2E2437DA-35E2-4D0D-B1DF-26E1DFEE1FB9}.Debug|x64.Build.0 = Debug|x64 + {2E2437DA-35E2-4D0D-B1DF-26E1DFEE1FB9}.Debug|x86.ActiveCfg = Debug|Win32 + {2E2437DA-35E2-4D0D-B1DF-26E1DFEE1FB9}.Debug|x86.Build.0 = Debug|Win32 + {2E2437DA-35E2-4D0D-B1DF-26E1DFEE1FB9}.Release|x64.ActiveCfg = Release|x64 + {2E2437DA-35E2-4D0D-B1DF-26E1DFEE1FB9}.Release|x64.Build.0 = Release|x64 + {2E2437DA-35E2-4D0D-B1DF-26E1DFEE1FB9}.Release|x86.ActiveCfg = Release|Win32 + {2E2437DA-35E2-4D0D-B1DF-26E1DFEE1FB9}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {750BD145-2459-46BB-A622-E3902280AE01} + EndGlobalSection +EndGlobal diff --git a/PTZControl/PTZControl.vcxproj b/PTZControl/PTZControl.vcxproj new file mode 100644 index 0000000..fabe125 --- /dev/null +++ b/PTZControl/PTZControl.vcxproj @@ -0,0 +1,224 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 16.0 + {2E2437DA-35E2-4D0D-B1DF-26E1DFEE1FB9} + MFCProj + PTZControl + 10.0 + + + + Application + true + v142 + Unicode + Static + + + Application + false + v142 + true + Unicode + Static + + + Application + true + v142 + Unicode + Static + + + Application + false + v142 + true + Unicode + Static + + + + + + + + + + + + + + + + + + + + + true + + + true + + + false + + + false + + + + Use + Level4 + true + WIN32;_WINDOWS;_DEBUG;%(PreprocessorDefinitions) + pch.h + + + Windows + + + false + true + _DEBUG;%(PreprocessorDefinitions) + + + 0x0409 + _DEBUG;%(PreprocessorDefinitions) + $(IntDir);%(AdditionalIncludeDirectories) + + + + + Use + Level3 + true + _WINDOWS;_DEBUG;%(PreprocessorDefinitions) + pch.h + + + Windows + + + false + true + _DEBUG;%(PreprocessorDefinitions) + + + 0x0409 + _DEBUG;%(PreprocessorDefinitions) + $(IntDir);%(AdditionalIncludeDirectories) + + + + + Use + Level4 + true + true + true + WIN32;_WINDOWS;NDEBUG;%(PreprocessorDefinitions) + pch.h + + + Windows + true + true + + + false + true + NDEBUG;%(PreprocessorDefinitions) + + + 0x0409 + NDEBUG;%(PreprocessorDefinitions) + $(IntDir);%(AdditionalIncludeDirectories) + + + + + Use + Level3 + true + true + true + _WINDOWS;NDEBUG;%(PreprocessorDefinitions) + pch.h + + + Windows + true + true + + + false + true + NDEBUG;%(PreprocessorDefinitions) + + + 0x0409 + NDEBUG;%(PreprocessorDefinitions) + $(IntDir);%(AdditionalIncludeDirectories) + + + + + + + + + + + + + + + + + Create + Create + Create + Create + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/PTZControl/PTZControlDlg.cpp b/PTZControl/PTZControlDlg.cpp new file mode 100644 index 0000000..a7307a5 --- /dev/null +++ b/PTZControl/PTZControlDlg.cpp @@ -0,0 +1,829 @@ + +// PTZControlDlg.cpp : implementation file +// + +#include "pch.h" +#include "framework.h" +#include "PTZControl.h" +#include "PTZControlDlg.h" +#include "SettingsDlg.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#endif + +////////////////////////////////////////////////////////////////////////////////////////// + +#define ON_BN_UNPUSHED(id, memberFxn) \ + ON_CONTROL(BN_UNPUSHED, id, memberFxn) + +////////////////////////////////////////////////////////////////////////////////////////// +// CPTZButton + +IMPLEMENT_DYNAMIC(CPTZButton,CMFCButton) + +CPTZButton::CPTZButton() + : m_bAutoRepeat(false) + , m_uiSent(0) +{ + +} + +void CPTZButton::PreSubclassWindow() +{ + __super::PreSubclassWindow(); + SetTooltip(_T("")); + // Setting delay time doesn't seam to work. + GetToolTipCtrl().SetDelayTime(0); +} + +void CPTZButton::OnDrawBorder(CDC* pDC, CRect& rectClient, UINT uiState) +{ + __super::OnDrawBorder(pDC,rectClient,uiState); + + // Give the button our button face. But only when the mouse isn't over + if (m_bWinXPTheme && m_clrFace!=COLORREF(-1)/* && !m_bHover && !m_bHighlighted*/) + pDC->FillSolidRect(rectClient, m_clrFace); +} + +void CPTZButton::OnDrawFocusRect(CDC* pDC, const CRect& rectClient) +{ + UNUSED_ALWAYS(pDC); UNUSED_ALWAYS(rectClient); +} + +void CPTZButton::OnLButtonDown(UINT nFlags, CPoint point) +{ + if (m_bAutoRepeat) + { + SetTimer(TIMER_AUTO_REPEAT, AUTO_REPEAT_INITIAL_DELAY, NULL); + m_uiSent = 0; + } + __super::OnLButtonDown(nFlags, point); +} + +void CPTZButton::OnLButtonUp(UINT nFlags, CPoint point) +{ + if (m_bAutoRepeat) + { + KillTimer(TIMER_AUTO_REPEAT); + + if (GetCapture() != NULL) + { + // If we never sent a message we do it once. + if (m_uiSent==0 && (GetState() & BST_PUSHED) != 0) + GetParent()->SendMessage(WM_COMMAND, MAKELONG(GetDlgCtrlID(), BN_CLICKED), (LPARAM)m_hWnd); + else + GetParent()->SendMessage(WM_COMMAND, MAKELONG(GetDlgCtrlID(), BN_UNPUSHED), (LPARAM)m_hWnd); + // release capture + ReleaseCapture(); + } + } + else + // Never call the default in auto repeat + __super::OnLButtonUp(nFlags, point); +} + +void CPTZButton::OnTimer(UINT_PTR nIDEvent) +{ + if (nIDEvent==TIMER_AUTO_REPEAT) + { + if (m_bAutoRepeat) + { + if ((GetState() & BST_PUSHED) == 0) + return; + ++m_uiSent; + SetTimer(TIMER_AUTO_REPEAT, AUTO_REPEAT_DELAY, NULL); + GetParent()->SendMessage(WM_COMMAND, MAKELONG(GetDlgCtrlID(), BN_CLICKED), (LPARAM)m_hWnd); + } + } + + __super::OnTimer(nIDEvent); +} + +////////////////////////////////////////////////////////////////////////////////////////// + +BOOL AdjustVisibleWindowRect(LPRECT lpRect, HWND hWndParent=NULL) +{ + RECT rcOrg(*lpRect); + + RECT rcBase; + if (hWndParent) + // Any parent given? + ::GetClientRect(hWndParent,&rcBase); + else + { + ::MONITORINFO mi; + mi.cbSize = sizeof(mi); + if (!AfxGetMainWnd()) + // If we have no main window we just try to use the main screen point 0,0 + ::GetMonitorInfo(::MonitorFromPoint(CPoint(0,0), MONITOR_DEFAULTTONEAREST), &mi); + else + // Get the main window monitor + ::GetMonitorInfo(::MonitorFromWindow(AfxGetMainWnd()->m_hWnd, MONITOR_DEFAULTTONEAREST), &mi); + rcBase = mi.rcWork; + } + + // To large? + if (lpRect->bottom-lpRect->top>rcBase.bottom-rcBase.top) + lpRect->bottom -= (lpRect->bottom-lpRect->top)-(rcBase.bottom-rcBase.top); + if (lpRect->right-lpRect->left>rcBase.right-rcBase.left) + lpRect->right -= (lpRect->right-lpRect->left)-(rcBase.right-rcBase.left); + + // Check if rect is visible + if (lpRect->bottom>rcBase.bottom) + ::OffsetRect(lpRect,0,rcBase.bottom-lpRect->bottom); + if (lpRect->toptop); + if (lpRect->right>rcBase.right) + ::OffsetRect(lpRect,rcBase.right-lpRect->right,0); + if (lpRect->leftleft,0); + + // Check if coords changes + return !EqualRect(lpRect,&rcOrg); +} + +////////////////////////////////////////////////////////////////////////////////////////// +// CPTZControlDlg dialog + +CPTZControlDlg::CPTZControlDlg(CWnd* pParent /*=nullptr*/) + : CDialogEx(IDD_PTZCONTROL_DIALOG, pParent) + , m_hAccel(NULL) + , m_iCurrentWebCam(0) + , m_iNumWebCams(0) + , m_evTerminating(FALSE,TRUE) + , m_pGuardThread(nullptr) +{ + m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME); + m_hAccel = ::LoadAccelerators(AfxFindResourceHandle(IDR_ACCELERATOR, RT_ACCELERATOR), MAKEINTRESOURCE(IDR_ACCELERATOR)); +} + +CPTZControlDlg::~CPTZControlDlg() +{ + for (auto &webcam : m_aWebCams) + webcam.CloseDevice(); +} + +void CPTZControlDlg::PostNcDestroy() +{ + __super::PostNcDestroy(); + + // Cleanup the guard thread. + m_evTerminating.SetEvent(); + if (m_pGuardThread) + ::WaitForSingleObject(m_pGuardThread->m_hThread,INFINITE); + m_pGuardThread = nullptr; + + // Finally delete the application. + delete this; +} + +BOOL CPTZControlDlg::OnCommand(WPARAM wParam, LPARAM lParam) +{ + return __super::OnCommand(wParam,lParam); +} + + +BOOL CPTZControlDlg::PreTranslateMessage(MSG* pMsg) +{ + if (pMsg->message>=WM_KEYFIRST && pMsg->message<=WM_KEYLAST) + { + if (m_hAccel) + { + if (::TranslateAccelerator(m_hWnd, m_hAccel, pMsg)) + return TRUE; + } + } + + return __super::PreTranslateMessage(pMsg); +} + +void CPTZControlDlg::DoDataExchange(CDataExchange* pDX) +{ + __super::DoDataExchange(pDX); + DDX_Control(pDX, IDC_BT_ZOOM_IN, m_btZoomIn); + DDX_Control(pDX, IDC_BT_ZOOM_OUT, m_btZoomOut); + DDX_Control(pDX, IDC_BT_UP, m_btUp); + DDX_Control(pDX, IDC_BT_DOWN, m_btDown); + DDX_Control(pDX, IDC_BT_HOME, m_btHome); + DDX_Control(pDX, IDC_BT_LEFT, m_btLeft); + DDX_Control(pDX, IDC_BT_RIGHT, m_btRight); + DDX_Control(pDX, IDC_BT_MEMORY, m_btMemory); + DDX_Control(pDX, IDC_BT_PRESET1, m_btPreset[0]); + DDX_Control(pDX, IDC_BT_PRESET2, m_btPreset[1]); + DDX_Control(pDX, IDC_BT_PRESET3, m_btPreset[2]); + DDX_Control(pDX, IDC_BT_PRESET4, m_btPreset[3]); + DDX_Control(pDX, IDC_BT_PRESET5, m_btPreset[4]); + DDX_Control(pDX, IDC_BT_PRESET6, m_btPreset[5]); + DDX_Control(pDX, IDC_BT_PRESET7, m_btPreset[6]); + DDX_Control(pDX, IDC_BT_PRESET8, m_btPreset[7]); + DDX_Control(pDX, IDC_BT_EXIT, m_btExit); + DDX_Control(pDX, IDC_BT_SETTINGS, m_btSettings); + DDX_Control(pDX, IDC_BT_WEBCAM1, m_btWebCam[0]); + DDX_Control(pDX, IDC_BT_WEBCAM2, m_btWebCam[1]); +} + +BEGIN_MESSAGE_MAP(CPTZControlDlg, CDialogEx) + ON_WM_PAINT() + ON_WM_QUERYDRAGICON() + ON_WM_CLOSE() + ON_WM_NCHITTEST() + ON_BN_CLICKED(IDC_BT_EXIT, &CPTZControlDlg::OnBtExit) + ON_WM_SETFOCUS() + ON_BN_CLICKED(IDC_BT_MEMORY, &CPTZControlDlg::OnBtMemory) + ON_COMMAND_EX(IDC_BT_WEBCAM1, &CPTZControlDlg::OnBtWebCam) + ON_COMMAND_EX(IDC_BT_WEBCAM2, &CPTZControlDlg::OnBtWebCam) + ON_COMMAND_EX(IDC_BT_PRESET1, &CPTZControlDlg::OnBtPreset) + ON_COMMAND_EX(IDC_BT_PRESET2, &CPTZControlDlg::OnBtPreset) + ON_COMMAND_EX(IDC_BT_PRESET3, &CPTZControlDlg::OnBtPreset) + ON_COMMAND_EX(IDC_BT_PRESET4, &CPTZControlDlg::OnBtPreset) + ON_COMMAND_EX(IDC_BT_PRESET5, &CPTZControlDlg::OnBtPreset) + ON_COMMAND_EX(IDC_BT_PRESET6, &CPTZControlDlg::OnBtPreset) + ON_COMMAND_EX(IDC_BT_PRESET7, &CPTZControlDlg::OnBtPreset) + ON_COMMAND_EX(IDC_BT_PRESET8, &CPTZControlDlg::OnBtPreset) + ON_BN_CLICKED(IDC_BT_ZOOM_IN, &CPTZControlDlg::OnBtZoomIn) + ON_BN_CLICKED(IDC_BT_ZOOM_OUT, &CPTZControlDlg::OnBtZoomOut) + ON_BN_CLICKED(IDC_BT_UP, &CPTZControlDlg::OnBtUp) + ON_BN_CLICKED(IDC_BT_DOWN, &CPTZControlDlg::OnBtDown) + ON_BN_CLICKED(IDC_BT_LEFT, &CPTZControlDlg::OnBtLeft) + ON_BN_CLICKED(IDC_BT_HOME, &CPTZControlDlg::OnBtHome) + ON_BN_CLICKED(IDC_BT_RIGHT, &CPTZControlDlg::OnBtRight) + ON_BN_CLICKED(IDC_BT_SETTINGS, &CPTZControlDlg::OnBtSettings) + ON_BN_UNPUSHED(IDC_BT_UP, &CPTZControlDlg::OnBtUnpushed) + ON_BN_UNPUSHED(IDC_BT_DOWN, &CPTZControlDlg::OnBtUnpushed) + ON_BN_UNPUSHED(IDC_BT_LEFT, &CPTZControlDlg::OnBtUnpushed) + ON_BN_UNPUSHED(IDC_BT_RIGHT, &CPTZControlDlg::OnBtUnpushed) + ON_WM_TIMER() +END_MESSAGE_MAP() + + +////////////////////////////////////////////////////////////////////////////////////////// +// CPTZControlDlg message handlers + +void CPTZControlDlg::ResetAllColors() +{ + for (CWnd* pWnd = GetWindow(GW_CHILD); pWnd; pWnd = pWnd->GetWindow(GW_HWNDNEXT)) + { + auto *pButton = DYNAMIC_DOWNCAST(CPTZButton, pWnd); + + // Don't reset color for web cam button + bool bIsWebCamBtn = false; + for (auto &btn : m_btWebCam) + bIsWebCamBtn |= pButton==&btn; + + // Reset color for all other buttons + if (pButton && !bIsWebCamBtn) + pButton->SetFaceColor(COLORREF(-1), TRUE); + } +} + +void CPTZControlDlg::ResetMemButton() +{ + // Clear the mem button + KillTimer(TIMER_CLEAR_MEMORY); + m_btMemory.SetCheck(0); + m_btMemory.SetFaceColor(COLORREF(-1)); +} + +CWebcamController& CPTZControlDlg::GetCurrentWebCam() +{ + return m_aWebCams[m_iCurrentWebCam]; +} + +void CPTZControlDlg::SetActiveCam(int iCam) +{ + if (iCam >= 0 && iCam < m_iNumWebCams) + { + // Clear mem button + ResetMemButton(); + + // Save the colors of the current buttons and set the old ones + for (CWnd* pWnd = GetWindow(GW_CHILD); pWnd; pWnd = pWnd->GetWindow(GW_HWNDNEXT)) + { + auto* pButton = DYNAMIC_DOWNCAST(CPTZButton, pWnd); + if (pButton) + { + // Save the color of the current buttons for the current web cam and get + // the saved color from the map we have for the new cam. + auto nId = pButton->GetDlgCtrlID(); + m_aMapBtnColors[m_iCurrentWebCam][nId] = pButton->GetFaceColor(); + auto it = m_aMapBtnColors[iCam].find(nId); + if (it!=m_aMapBtnColors[iCam].end()) + pButton->SetFaceColor(it->second); + } + } + + // Set the tooltips + for (int i=0; i(p); + + // This thread end when the application exists. + // For ever all 2000 seconds check if the application still runs and accepts messages. + for (;;) + { + // Check if we have an event, wait for 1 sec + CSingleLock lock(&pWnd->m_evTerminating,FALSE); + if (lock.Lock(1000)) + // Event is set. + return 0; + + DWORD dwResult = 0; + // Check if the application is blocking + if (::SendMessageTimeout(pWnd->GetSafeHwnd(), WM_NULL, 0, 0, SMTO_ABORTIFHUNG | SMTO_NORMAL, 1000, &dwResult)==0) + { + // Exit the current process + ::ExitProcess(10); + } + } +} + +BOOL CPTZControlDlg::OnInitDialog() +{ + __super::OnInitDialog(); + + // Set the icon for this dialog. The framework does this automatically + // when the application's main window is not a dialog + SetIcon(m_hIcon, TRUE); // Set big icon + SetIcon(m_hIcon, FALSE); // Set small icon + + // Load the bitmap + { + CMFCToolBarImages images; + images.SetImageSize(CSize(16, 16)); + images.Load(IDB_BUTTONS); + + int iImage = 0; + CPTZButton* apButtons[] = + { + &m_btDown, + &m_btLeft, + &m_btRight, + &m_btUp, + &m_btHome, + &m_btZoomIn, + &m_btZoomOut, + &m_btMemory, + &m_btPreset[0], + &m_btPreset[1], + &m_btPreset[2], + &m_btPreset[3], + &m_btPreset[4], + &m_btPreset[5], + &m_btPreset[6], + &m_btPreset[7], + &m_btExit, + &m_btSettings, + &m_btWebCam[0], + &m_btWebCam[1], + }; + for (auto* pBtn : apButtons) + { + HICON hIcon = images.ExtractIcon(iImage++); + pBtn->SetImage(hIcon); + // we have a graphic now, we skip the text + pBtn->SetWindowText(_T("")); + } + } + + // Set auto repeat buttons + m_btLeft.SetAutoRepeat(true); + m_btRight.SetAutoRepeat(true); + m_btDown.SetAutoRepeat(true); + m_btUp.SetAutoRepeat(true); + m_btZoomIn.SetAutoRepeat(true); + m_btZoomOut.SetAutoRepeat(true); + + // This is a check box style + m_btMemory.SetCheckStyle(); + for (auto &btn : m_btWebCam) + btn.SetCheckStyle(); + + // First Center + CenterWindow(); + + // Adjust the window + CRect rect; + GetWindowRect(rect); + CPoint pt = rect.TopLeft(); + rect.OffsetRect(-pt); + pt.x = theApp.GetProfileInt(REG_WINDOW,REG_WINDOW_POSX,pt.x); + pt.y = theApp.GetProfileInt(REG_WINDOW,REG_WINDOW_POSY,pt.y); + rect.OffsetRect(pt); + AdjustVisibleWindowRect(rect); + + // Move it + SetWindowPos(&CWnd::wndTopMost, rect.left, rect.top, 0, 0, SWP_NOSIZE); + + // Try to find the Device list + CStringArray aDevices; + CWebcamController::ListDevices(aDevices); + + auto OpenWebCam = [](CWebcamController& webCam, CString strDevToken)->bool + { + HRESULT hr = webCam.OpenDevice(CComBSTR(strDevToken), 0, 0); + if (FAILED(hr)) + { + AfxMessageBox(IDP_ERR_OPENFAILED); + return false; + } + + // Load the default setting + webCam.UseLogitechMotionControl(theApp.GetProfileInt(REG_DEVICE, REG_USELOGOTECHMOTIONCONTROL, FALSE) != 0); + webCam.SetMotorIntervalTimer(theApp.GetProfileInt(REG_DEVICE, REG_MOTORINTERVALTIMER, DEFAULT_MOTOR_INTERVAL_TIMER)); + return true; + }; + + // Find the devices to search for. We have some default devices if no other is set in + // the registry o on the command line. +CStringArray aStrCameraNameToSearch; + aStrCameraNameToSearch.Add(DEFAULT_DEVICE_NAME_1); + aStrCameraNameToSearch.Add(DEFAULT_DEVICE_NAME_2); + CString strCameraNameToSearch = theApp.GetProfileString(REG_DEVICE,REG_DEVICENAME,_T("")); + if (!strCameraNameToSearch.IsEmpty()) + aStrCameraNameToSearch.Add(strCameraNameToSearch); + if (!theApp.m_strDevName.IsEmpty()) + aStrCameraNameToSearch.Add(theApp.m_strDevName); + + // Search for the PTZ PRO 2 + CString strDevToken, strCameras; + for (int i = 0; i0) + SetActiveCam(0); + } + else + { + // Reset the cameras to home position. and leave the first camera + // (index 0) the active one ((loop backwards). + for (int i = m_iNumWebCams; i > 0; --i) + { + SetActiveCam(i - 1); + OnBtHome(); + } + } + + return FALSE; +} + +// If you add a minimize button to your dialog, you will need the code below +// to draw the icon. For MFC applications using the document/view model, +// this is automatically done for you by the framework. + +void CPTZControlDlg::OnPaint() +{ + if (IsIconic()) + { + CPaintDC dc(this); // device context for painting + + SendMessage(WM_ICONERASEBKGND, reinterpret_cast(dc.GetSafeHdc()), 0); + + // Center icon in client rectangle + int cxIcon = GetSystemMetrics(SM_CXICON); + int cyIcon = GetSystemMetrics(SM_CYICON); + CRect rect; + GetClientRect(&rect); + int x = (rect.Width() - cxIcon + 1) / 2; + int y = (rect.Height() - cyIcon + 1) / 2; + + // Draw the icon + dc.DrawIcon(x, y, m_hIcon); + } + else + { + __super::OnPaint(); + } +} + +// The system calls this function to obtain the cursor to display while the user drags +// the minimized window. +HCURSOR CPTZControlDlg::OnQueryDragIcon() +{ + return static_cast(m_hIcon); +} + +LRESULT CPTZControlDlg::OnNcHitTest(CPoint point) +{ + // Allow drag & drop + LRESULT lResult = __super::OnNcHitTest(point); + if (lResult == HTCLIENT) + lResult = HTCAPTION; + return lResult; +} + +void CPTZControlDlg::OnClose() +{ + CRect rect; + GetWindowRect(rect); + theApp.WriteProfileInt(REG_WINDOW,REG_WINDOW_POSX,rect.left); + theApp.WriteProfileInt(REG_WINDOW,REG_WINDOW_POSY,rect.top); + + DestroyWindow(); +} + +void CPTZControlDlg::OnBtExit() +{ + PostMessage(WM_CLOSE); +} + + +void CPTZControlDlg::OnOK() +{ + // Ignore +} + + +void CPTZControlDlg::OnCancel() +{ + // Ignore +} + + + +void CPTZControlDlg::OnBtMemory() +{ + ResetAllColors(); + bool bCheck = !m_btMemory.GetCheck(); + m_btMemory.SetCheck(bCheck); + if (bCheck) + { + // Remember the mem button, but clear it after some delay. + m_btMemory.SetFaceColor(COLOR_RED, TRUE); + SetTimer(TIMER_CLEAR_MEMORY,CLEAR_MEMORY_DELAY,nullptr); + } + else + m_btMemory.SetFaceColor(COLORREF(-1),TRUE); +} + +BEGIN_MESSAGE_MAP(CPTZButton, CMFCButton) + ON_WM_LBUTTONDOWN() + ON_WM_TIMER() + ON_WM_LBUTTONUP() +END_MESSAGE_MAP() + + +BOOL CPTZControlDlg::OnBtPreset(UINT nId) +{ + UINT uiPreset = 0; + while (uiPreset(m_btPreset[uiPreset].GetDlgCtrlID())==nId) + break; + ++uiPreset; + } + + if (uiPresetSetFaceColor(COLOR_GREEN,TRUE); + } + return TRUE; +} + +BOOL CPTZControlDlg::OnBtWebCam(UINT nId) +{ + SetActiveCam(nId==IDC_BT_WEBCAM1 ? 0 : 1); + return 1; +} + +void CPTZControlDlg::OnBtHome() +{ + ResetAllColors(); + GetCurrentWebCam().GotoHome(); + m_btHome.SetFaceColor(COLOR_GREEN,TRUE); +} + + +void CPTZControlDlg::OnBtZoomIn() +{ + ResetAllColors(); + GetCurrentWebCam().Zoom(1); +} + + +void CPTZControlDlg::OnBtZoomOut() +{ + ResetAllColors(); + GetCurrentWebCam().Zoom(-1); +} + + +void CPTZControlDlg::OnBtDown() +{ + ResetAllColors(); + if (m_btDown.InAutoRepeat()) + GetCurrentWebCam().Tilt(-1); + else + GetCurrentWebCam().MoveTilt(-1); +} + +void CPTZControlDlg::OnBtUp() +{ + ResetAllColors(); + if (m_btUp.InAutoRepeat()) + GetCurrentWebCam().Tilt(1); + else + GetCurrentWebCam().MoveTilt(1); +} + + +void CPTZControlDlg::OnBtLeft() +{ + ResetAllColors(); + if (m_btLeft.InAutoRepeat()) + GetCurrentWebCam().Pan(-1); + else + GetCurrentWebCam().MovePan(-1); +} + + +void CPTZControlDlg::OnBtRight() +{ + ResetAllColors(); + if (m_btRight.InAutoRepeat()) + GetCurrentWebCam().Pan(1); + else + GetCurrentWebCam().MovePan(1); +} + + +void CPTZControlDlg::OnSetFocus(CWnd* pOldWnd) +{ + // Do nothing. Never set the focus to a child. + UNUSED_ALWAYS(pOldWnd); +} + + + +void CPTZControlDlg::OnTimer(UINT_PTR nIDEvent) +{ + if (nIDEvent == TIMER_FOCUS_CHECK) + { + CWnd* pWndFocus = GetFocus(); + if (pWndFocus && + pWndFocus->GetParent()==this && + (GetAsyncKeyState(VK_LBUTTON) & 0x8000)==0) + // only move the focus, when a button has the focus and the mouse is not down. + SetFocus(); + } + else if (nIDEvent == TIMER_CLEAR_MEMORY) + { + // Clear the mem button after some delay + ResetMemButton(); + } + + __super::OnTimer(nIDEvent); +} + +void CPTZControlDlg::OnBtUnpushed() +{ + GetCurrentWebCam().Pan(0); + GetCurrentWebCam().Tilt(0); +} + +void CPTZControlDlg::OnBtSettings() +{ + CSettingsDlg dlg; + dlg.m_strCameraName = m_strCameraDeviceName; + dlg.m_bLogitechCameraControl = GetCurrentWebCam().UseLogitechMotionControl(); + dlg.m_iMotorIntervalTimer = GetCurrentWebCam().GetMotorIntervalTimer(); + + // Get a copy of the tooltips + for (int i = 0; i < CWebcamController::NUM_PRESETS; ++i) + { + for (int j = 0; j < CPTZControlDlg::NUM_MAX_WEBCAMS; ++j) + { + dlg.m_strTooltip[j][i] = m_strTooltips[j][i]; + } + } + + if (dlg.DoModal()!=IDOK) + return; + + // Copy back and save + for (int i = 0; i < CWebcamController::NUM_PRESETS; ++i) + { + for (int j = 0; j < CPTZControlDlg::NUM_MAX_WEBCAMS; ++j) + { + m_strTooltips[j][i] = dlg.m_strTooltip[j][i]; + CString str; + str.Format(REG_TOOLTIP, i + 1 + j*100); + theApp.WriteProfileString(REG_WINDOW, str, m_strTooltips[j][i]); + } + } + + // Camera control + GetCurrentWebCam().UseLogitechMotionControl(dlg.m_bLogitechCameraControl!=0); + theApp.WriteProfileInt(REG_DEVICE, REG_USELOGOTECHMOTIONCONTROL, dlg.m_bLogitechCameraControl); + GetCurrentWebCam().SetMotorIntervalTimer(dlg.m_iMotorIntervalTimer); + theApp.WriteProfileInt(REG_DEVICE, REG_USELOGOTECHMOTIONCONTROL, dlg.m_bLogitechCameraControl); + + // Set tooltips again + SetActiveCam(m_iCurrentWebCam); +} + diff --git a/PTZControl/PTZControlDlg.h b/PTZControl/PTZControlDlg.h new file mode 100644 index 0000000..e3e7318 --- /dev/null +++ b/PTZControl/PTZControlDlg.h @@ -0,0 +1,151 @@ + +// PTZControlDlg.h : header file +// + +#pragma once +#include "resource.h" +#include "ExtensionUnit.h" + +////////////////////////////////////////////////////////////////////////////////////////// +// CPTZButton + +class CPTZButton : public CMFCButton +{ +DECLARE_DYNAMIC(CPTZButton) +public: + CPTZButton(); + void SetCheckStyle() + { + m_bCheckButton = TRUE; + m_bAutoCheck = FALSE; + } + + void SetAutoRepeat(bool bVal) + { + m_bAutoRepeat = bVal; + } + bool InAutoRepeat() + { + return m_uiSent!=0; + } + COLORREF GetFaceColor() + { + return m_clrFace; + } + + void SetTooltip(LPCTSTR lpszToolTipText) + { + // We keep an internal copy of the tooltip + m_strTooltip = lpszToolTipText; + __super::SetTooltip(lpszToolTipText); + } + CString GetTooltip() + { + return m_strTooltip; + } + +protected: +// Data + bool m_bAutoRepeat; + UINT m_uiSent; + CString m_strTooltip; + +protected: + void PreSubclassWindow() override; + void OnDrawBorder(CDC* pDC, CRect& rectClient, UINT uiState) override; + void OnDrawFocusRect(CDC* pDC, const CRect& rectClient) override; + +public: + DECLARE_MESSAGE_MAP() + afx_msg void OnLButtonDown(UINT nFlags, CPoint point); + afx_msg void OnTimer(UINT_PTR nIDEvent); + afx_msg void OnLButtonUp(UINT nFlags, CPoint point); +}; + +////////////////////////////////////////////////////////////////////////////////////////// +// CPTZControlDlg dialog + +class CPTZControlDlg : public CDialogEx +{ +// Construction +public: + CPTZControlDlg(CWnd* pParent = nullptr); // standard constructor + ~CPTZControlDlg(); +// Dialog Data + enum { IDD = IDD_PTZCONTROL_DIALOG }; + static const int NUM_MAX_WEBCAMS = 2; + +protected: + CPTZButton m_btZoomIn; + CPTZButton m_btZoomOut; + CPTZButton m_btUp; + CPTZButton m_btDown; + CPTZButton m_btHome; + CPTZButton m_btLeft; + CPTZButton m_btRight; + CPTZButton m_btMemory; + CPTZButton m_btPreset[CWebcamController::NUM_PRESETS]; + CPTZButton m_btExit; + CPTZButton m_btSettings; + CPTZButton m_btWebCam[NUM_MAX_WEBCAMS]; + +// The controller + CWebcamController m_aWebCams[NUM_MAX_WEBCAMS]; + +// Map to save the colors of the buttons per Webcam + typedef std::map TMAP_BTNCOLORS; + TMAP_BTNCOLORS m_aMapBtnColors[NUM_MAX_WEBCAMS]; + +// Tooltips per WebCam + CString m_strTooltips[NUM_MAX_WEBCAMS][CWebcamController::NUM_PRESETS]; + + HACCEL m_hAccel; + CString m_strCameraDeviceName; + int m_iCurrentWebCam; // zero based index to m_aWebCams + int m_iNumWebCams; + + void ResetAllColors(); + CWebcamController &GetCurrentWebCam(); + void SetActiveCam(int iCam); + + // Guard thread + CEvent m_evTerminating; + CWinThread* m_pGuardThread; + static UINT AFX_CDECL GuardThread(LPVOID hWnd); // AFX_THREADPROC + +// Implementation +protected: + HICON m_hIcon; + + // Generated message map functions + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + virtual BOOL OnInitDialog(); + virtual void PostNcDestroy(); + virtual void OnOK(); + virtual void OnCancel(); + virtual BOOL PreTranslateMessage(MSG* pMsg); + virtual BOOL OnCommand(WPARAM wParam, LPARAM lParam) override; + afx_msg void OnPaint(); + afx_msg HCURSOR OnQueryDragIcon(); + DECLARE_MESSAGE_MAP() + afx_msg void OnBtExit(); + afx_msg void OnClose(); + afx_msg LRESULT OnNcHitTest(CPoint point); + afx_msg void OnBtMemory(); + afx_msg BOOL OnBtPreset(UINT nId); + + void ResetMemButton(); + + afx_msg BOOL OnBtWebCam(UINT nId); + afx_msg void OnSetFocus(CWnd* pOldWnd); + afx_msg void OnBtZoomIn(); + afx_msg void OnBtZoomOut(); + afx_msg void OnBtUp(); + afx_msg void OnBtLeft(); + afx_msg void OnBtHome(); + afx_msg void OnBtRight(); + afx_msg void OnBtDown(); + afx_msg void OnTimer(UINT_PTR nIDEvent); + afx_msg void OnBtUnpushed(); + afx_msg void OnBtSettings(); +}; diff --git a/PTZControl/SettingsDlg.cpp b/PTZControl/SettingsDlg.cpp new file mode 100644 index 0000000..caefd90 --- /dev/null +++ b/PTZControl/SettingsDlg.cpp @@ -0,0 +1,77 @@ +// SettingsDlg.cpp : implementation file +// + +#include "pch.h" +#include "PTZControl.h" +#include "SettingsDlg.h" +#include "afxdialogex.h" + + +// CSettingsDlg dialog + +IMPLEMENT_DYNAMIC(CSettingsDlg, CDialogEx) + +CSettingsDlg::CSettingsDlg(CWnd* pParent /*=nullptr*/) + : CDialogEx(IDD_SETTINGS, pParent) + , m_bLogitechCameraControl(FALSE) + , m_iMotorIntervalTimer(0) +{ + +} + +CSettingsDlg::~CSettingsDlg() +{ +} + +void CSettingsDlg::DoDataExchange(CDataExchange* pDX) +{ + CDialogEx::DoDataExchange(pDX); + DDX_Text(pDX, IDC_ED_CAMERA, m_strCameraName); + DDX_Text(pDX, IDC_ED_TOOLTIP_1_1, m_strTooltip[0][0]); + DDX_Text(pDX, IDC_ED_TOOLTIP_1_2, m_strTooltip[0][1]); + DDX_Text(pDX, IDC_ED_TOOLTIP_1_3, m_strTooltip[0][2]); + DDX_Text(pDX, IDC_ED_TOOLTIP_1_4, m_strTooltip[0][3]); + DDX_Text(pDX, IDC_ED_TOOLTIP_1_5, m_strTooltip[0][4]); + DDX_Text(pDX, IDC_ED_TOOLTIP_1_6, m_strTooltip[0][5]); + DDX_Text(pDX, IDC_ED_TOOLTIP_1_7, m_strTooltip[0][6]); + DDX_Text(pDX, IDC_ED_TOOLTIP_1_8, m_strTooltip[0][7]); + DDX_Text(pDX, IDC_ED_TOOLTIP_2_1, m_strTooltip[1][0]); + DDX_Text(pDX, IDC_ED_TOOLTIP_2_2, m_strTooltip[1][1]); + DDX_Text(pDX, IDC_ED_TOOLTIP_2_3, m_strTooltip[1][2]); + DDX_Text(pDX, IDC_ED_TOOLTIP_2_4, m_strTooltip[1][3]); + DDX_Text(pDX, IDC_ED_TOOLTIP_2_5, m_strTooltip[1][4]); + DDX_Text(pDX, IDC_ED_TOOLTIP_2_6, m_strTooltip[1][5]); + DDX_Text(pDX, IDC_ED_TOOLTIP_2_7, m_strTooltip[1][6]); + DDX_Text(pDX, IDC_ED_TOOLTIP_2_8, m_strTooltip[1][7]); + DDX_Check(pDX, IDC_CH_LOGITECHCONTROL, m_bLogitechCameraControl); + DDX_Text(pDX, IDC_ED_MOTORTIME, m_iMotorIntervalTimer); + DDX_Control(pDX, IDC_ED_MOTORTIME, m_edMotorInterval); + DDX_Control(pDX, IDC_CH_LOGITECHCONTROL, m_chLogitechControl); + if (pDX->m_bSaveAndValidate) + m_iMotorIntervalTimer = min(max(10,m_iMotorIntervalTimer),1000); +} + + +BEGIN_MESSAGE_MAP(CSettingsDlg, CDialogEx) + ON_BN_CLICKED(IDC_CH_LOGITECHCONTROL, &CSettingsDlg::OnChLogitechcontrol) +END_MESSAGE_MAP() + + +// CSettingsDlg message handlers + + +void CSettingsDlg::OnChLogitechcontrol() +{ + m_edMotorInterval.EnableWindow(!m_chLogitechControl.GetCheck()); +} + + +BOOL CSettingsDlg::OnInitDialog() +{ + CDialogEx::OnInitDialog(); + + OnChLogitechcontrol(); + + CenterWindow(); + return TRUE; +} diff --git a/PTZControl/SettingsDlg.h b/PTZControl/SettingsDlg.h new file mode 100644 index 0000000..44fefbe --- /dev/null +++ b/PTZControl/SettingsDlg.h @@ -0,0 +1,34 @@ +#pragma once + +#include "ExtensionUnit.h" +#include "PTZControlDlg.h" + +// CSettingsDlg dialog + +class CSettingsDlg : public CDialogEx +{ + DECLARE_DYNAMIC(CSettingsDlg) + +public: + CSettingsDlg(CWnd* pParent = nullptr); // standard constructor + virtual ~CSettingsDlg(); + +// Dialog Data +#ifdef AFX_DESIGN_TIME + enum { IDD = IDD_SETTINGS }; +#endif +public: + CString m_strCameraName; + CString m_strTooltip[CPTZControlDlg::NUM_MAX_WEBCAMS][CWebcamController::NUM_PRESETS]; + +protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + DECLARE_MESSAGE_MAP() +public: + BOOL m_bLogitechCameraControl; + int m_iMotorIntervalTimer; + afx_msg void OnChLogitechcontrol(); + CEdit m_edMotorInterval; + CButton m_chLogitechControl; + virtual BOOL OnInitDialog(); +}; diff --git a/PTZControl/framework.h b/PTZControl/framework.h new file mode 100644 index 0000000..c700568 --- /dev/null +++ b/PTZControl/framework.h @@ -0,0 +1,31 @@ +#pragma once + +#ifndef VC_EXTRALEAN +#define VC_EXTRALEAN // Exclude rarely-used stuff from Windows headers +#endif + +#include "targetver.h" + +#define _ATL_CSTRING_EXPLICIT_CONSTRUCTORS // some CString constructors will be explicit + +// turns off MFC's hiding of some common and often safely ignored warning messages +#define _AFX_ALL_WARNINGS + +#include // MFC core and standard components +#include // MFC extensions + +#ifndef _AFX_NO_OLE_SUPPORT +#include // MFC support for Internet Explorer 4 Common Controls +#endif +#ifndef _AFX_NO_AFXCMN_SUPPORT +#include // MFC support for Windows Common Controls +#endif // _AFX_NO_AFXCMN_SUPPORT + +#include // MFC support for ribbons and control bars +#include + +#include +#include +#include +#include + diff --git a/PTZControl/pch.cpp b/PTZControl/pch.cpp new file mode 100644 index 0000000..64b7eef --- /dev/null +++ b/PTZControl/pch.cpp @@ -0,0 +1,5 @@ +// pch.cpp: source file corresponding to the pre-compiled header + +#include "pch.h" + +// When you are using pre-compiled headers, this source file is necessary for compilation to succeed. diff --git a/PTZControl/pch.h b/PTZControl/pch.h new file mode 100644 index 0000000..885d5d6 --- /dev/null +++ b/PTZControl/pch.h @@ -0,0 +1,13 @@ +// pch.h: This is a precompiled header file. +// Files listed below are compiled only once, improving build performance for future builds. +// This also affects IntelliSense performance, including code completion and many code browsing features. +// However, files listed here are ALL re-compiled if any one of them is updated between builds. +// Do not add files here that you will be updating frequently as this negates the performance advantage. + +#ifndef PCH_H +#define PCH_H + +// add headers that you want to pre-compile here +#include "framework.h" + +#endif //PCH_H diff --git a/PTZControl/res/CameraControl.bmp b/PTZControl/res/CameraControl.bmp new file mode 100644 index 0000000..8437d55 Binary files /dev/null and b/PTZControl/res/CameraControl.bmp differ diff --git a/PTZControl/res/PTZControl.ico b/PTZControl/res/PTZControl.ico new file mode 100644 index 0000000..0007556 Binary files /dev/null and b/PTZControl/res/PTZControl.ico differ diff --git a/PTZControl/res/PTZControl.rc2 b/PTZControl/res/PTZControl.rc2 new file mode 100644 index 0000000..20f478f Binary files /dev/null and b/PTZControl/res/PTZControl.rc2 differ diff --git a/PTZControl/resource.h b/PTZControl/resource.h new file mode 100644 index 0000000..53c53e3 --- /dev/null +++ b/PTZControl/resource.h @@ -0,0 +1,66 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by PTZControl.rc +// +#define IDD_PTZCONTROL_DIALOG 102 +#define IDD_PTZCONTROL 102 +#define IDR_MAINFRAME 128 +#define IDB_BUTTONS 130 +#define IDP_ERR_NO_CAMERA 131 +#define IDP_ERR_OPENFAILED 132 +#define IDR_ACCELERATOR 132 +#define IDD_SETTINGS 133 +#define IDP_TXT_CAMERAS 133 +#define IDC_BT_LEFT 1000 +#define IDC_BT_RIGHT 1001 +#define IDC_CHECK1 1001 +#define IDC_CH_LOGITECHCONTROL 1001 +#define IDC_BT_UP 1002 +#define IDC_ED_TOOLTIP_1_1 1002 +#define IDC_BT_DOWN 1003 +#define IDC_ED_TOOLTIP_1_2 1003 +#define IDC_BT_HOME 1004 +#define IDC_ED_TOOLTIP_1_3 1004 +#define IDC_BT_MEMORY 1005 +#define IDC_ED_TOOLTIP_1_4 1005 +#define IDC_BT_PRESET1 1006 +#define IDC_ED_TOOLTIP_1_5 1006 +#define IDC_BT_PRESET2 1007 +#define IDC_ED_TOOLTIP_1_6 1007 +#define IDC_BT_PRESET3 1008 +#define IDC_ED_TOOLTIP_1_7 1008 +#define IDC_BT_PRESET4 1009 +#define IDC_ED_TOOLTIP_1_8 1009 +#define IDC_BT_PRESET5 1010 +#define IDC_ED_CAMERA 1010 +#define IDC_BT_PRESET6 1011 +#define IDC_EDIT1 1011 +#define IDC_ED_MOTORTIME 1011 +#define IDC_BT_PRESET7 1012 +#define IDC_ED_TOOLTIP_2_1 1012 +#define IDC_BT_PRESET8 1013 +#define IDC_ED_TOOLTIP_2_2 1013 +#define IDC_BT_ZOOM_IN 1014 +#define IDC_ED_TOOLTIP_2_3 1014 +#define IDC_BT_ZOOM_OUT 1015 +#define IDC_ED_TOOLTIP_2_4 1015 +#define IDC_BT_EXIT 1016 +#define IDC_ED_TOOLTIP_2_5 1016 +#define IDC_BT_SETTINGS 1017 +#define IDC_ED_TOOLTIP_2_6 1017 +#define IDC_BT_WEBCAM1 1018 +#define IDC_ED_TOOLTIP_2_7 1018 +#define IDC_BT_WEBCAM2 1019 +#define IDC_ED_TOOLTIP_2_8 1019 +#define DC_BT_SETTINGS 32791 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 135 +#define _APS_NEXT_COMMAND_VALUE 32793 +#define _APS_NEXT_CONTROL_VALUE 1012 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/PTZControl/targetver.h b/PTZControl/targetver.h new file mode 100644 index 0000000..87c0086 --- /dev/null +++ b/PTZControl/targetver.h @@ -0,0 +1,8 @@ +#pragma once + +// Including SDKDDKVer.h defines the highest available Windows platform. + +// If you wish to build your application for a previous Windows platform, include WinSDKVer.h and +// set the _WIN32_WINNT macro to the platform you wish to support before including SDKDDKVer.h. + +#include diff --git a/README.md b/README.md index 084adbe..a56113e 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,90 @@ # PTZControl - Pan Tilt Zoom Control for Logitech PTZ2 Pro and Rally cameras + +## History +This small program is designed to control a Logitech PTZ 2 Pro. The camera was purchased to stream our church services. +Unfortunately, it quickly turned out that the operation with the remote control is possible but cumbersome and inaccurate, since the camera was installed directly behind and above the video technician. So I made the decision to build a corresponding control program. +Some month later we bought a second camera and the program was extended by the function to control another camera. A maximum of two cameras are supported. +And finally I added support for the Logitech Rally. + +## Used interfaces +The program directly uses the camera media control via the Windows SDK (see links in the source code). +For controlling the zoom functions and the preset functions of the PTZ 2 Pro, Logitech provided me with some example code with defines from the Lync driver. +Logitech internal interfaces are used for: +- Saving the presets, +- Getting the presets +- For step-by-step Pan Tilt camera control (see also the settings dialog: Check "Use LogitechCamera Motion Control") + +Standard controls from the Windows Driver API are used +- For zoom control +- For the timer-controlled PT (Pan Tilt) control (see Settings: no check mark at "Use Logitech Camera Motion Control")This control is the standard. + +The program supports tooltips that and you can define them yourself to give the camera presets useful names. (Separate for each camera) + +## Used environment and libraries +I used the Visual Studoi 2019 Community Edition to develop this program with C++. +MFC and ATL as the library. No additional software is used. +The EXE runs alone, without installing any other files. + +## Behaviour +The program is always in the foreground and has been designed relatively compact and small, so that you can hover somewhere over your OBS program and it is really easy to use. +Current selected preset or home position are shown with a green background on the buttons. +Recalling a presets are simply done by clicking on one of the number buttons. +Presets are changed by pressing the M button (Memory) followed by a number key. The M button turns red as long as it is active. + +The program remembers its last position on the screen and is automatically repositioned to when it is started. +All settings are stored in the registry under the branch `HKEY_CURRENT_USER\SOFTWARE\MRi-Software\PTZControl`. +See the command line secion too. + +### Supported Cameras +Currently, the Logitech PTZ 2 Pro and Logitech Rally cameras are automatically detected. +For other cameras, you can try to force detection by specifying the name (or part of the name) of the cameras in the registry or on the command line. + +Internally, all cameras that have one of the following tokens in the name are automatically used: +- *PTZ 2 Pro* +- *Logi Rally* + +### Guard Thread +Unfortunately, we have sometimes had the experience that OBS or the USB bus hangs with a camera. The PTZControl program then usually stops and stops responding because the camera control commands block the application. +Through an internal guard thread, the application can determine that it is no longer working correctly and terminates automatically. +Otherwise you would have to use the task manager and this can take a lot of time to terminate the application in the hustle and bustle of a livestream. + +### Logitech Motion Control +The Logitech cameras have their own interface for pan/tilt control. This moves the camera in X/Y Axe by a certain step value, This control is a special Logitech feature. +If you click on a direction button once, exactly one step pulse is output. +If you hold down a direction button, a pulse is sent to the camera again and again at a certain interval to change the direction. + +### Standard Motion Control +This control is the standard when you start the application for the first time. +I have made the experience that this Logitech motion control is a bit rough. Via the normal device control, a pan/tilt is also possible in corresponding motor commands for X/Y direction. This is done in turning on the motor for a specific time interval and turning it off again. +Accordingly, you can adjust the timer interval for Motor on/off accordingly. The default is 70msec. Values between 70 and 100 or goiod values. +If you click on a direction button once, the motor is turned on and off again after the corresponding interval. +If the direction button remains pressed, the motor remains switched on for the corresponding direction until the button is released again. +This control seems more effective and accurate to me and is the standard. The disadvantage is that if the timer interval is too small, the camera does not react immediately when a button is clicked. But since precision was more important to me because our camera is installed relatively far away from the podium, I use this setting with a 70msec timer. + +## Command Line Options +A few options can be set from the command line. Command-line switches override the settings in the registry. + +**-device:"name of device"** +The device option can be used to specify a name component of a camera to be used for control. +If you enter "*" as the name, then each camera will be recognized. + +**-showdevices** +Displays a message box after startup showing the name(s) of the detected cameras. + +**-noreset** +At startup, a detected camera is moved to the home position (Logitech Preset) and the zoom is reset to maximum wide angle. If the -noreset option is specified, the camera position remains unchanged. + +**-noguard** +-noguard prevents the application from terminating itself in a controlled manner. This can be especially important in the event of a bug and for testing. + +## Registry +In the registry branch `HKEY_CURRENT_USER\SOFTWARE\MRi-Software\PTZControl\Options` it is possible to preset the following options without using the command line. + +**NoReset (DWORD value** +*Value <>0:* Has the same function as -noreset on the command line. The current camera and zoom position is maintained when starting the program. Value = 0: When starting the program, you move to the home position and zoom to maximum wide angle. (Default) + +**NoGuard (DWORD value)** +*Value <>0:* The guard thread that may automatically terminate the application is terminated. +*Value = 0:* The guard thread automatically terminates the application if a blocking of the USB bus is detected. (Default) + +