diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt index 65754c0b17..c33d8eb07b 100644 --- a/.github/actions/spell-check/expect.txt +++ b/.github/actions/spell-check/expect.txt @@ -46,6 +46,7 @@ Aissue akamaihd ALarger alekhyareddy +alignas ALIGNLEFT ALLAPPS Alloc @@ -142,9 +143,11 @@ autoplay Autorun AUTOSIZECOLUMNS autoupdate -awakeversion AValid +avialable +awakeversion AWAYMODE +AYUV azurecr azurewebsites backend @@ -185,6 +188,7 @@ bms BNumber Bokm BOKMAL +boolalpha Bools bootstrapper Bopomofo @@ -213,6 +217,7 @@ BValue bytearray callbackptr callhistory +Camer Cangjie cangjieime CANRENAME @@ -285,6 +290,7 @@ CMock CMONITORS cmyk cnt +coc coclass codebase codecvt @@ -293,9 +299,10 @@ codereview Codespaces COINIT colorconv +colorfilter colorhistory colorhistorylimit -colorfilter +COLORKEY colorpicker COLORREF colorscheme @@ -429,6 +436,7 @@ dcomp DComposition ddd ddee +ddf Deact declspec decltype @@ -472,11 +480,14 @@ devenum deviceencryption devicemanagenent DEVMON +devpkey +DEVSOURCE DFactory DHCP Dialpad diffing difftime +DIIRFLAG dimm directaccess dirname @@ -489,6 +500,7 @@ Displayandhidethedesktop DISPLAYCHANGE displayname divyan +djsoref DLACTIVEXCTLS DLCONTROL dlg @@ -501,9 +513,10 @@ dllexport dllhost dllmain DNLEN -docsmsft Dns +docsmsft doctype +dogancelik domainlexicon DONTVALIDATEPATH dotnet @@ -531,7 +544,11 @@ dupenv dutil DVASPECT DVASPECTINFO +DVH +DVHD DVR +DVSD +DVSL DVTARGETDEVICE DWindow DWINRT @@ -550,6 +567,8 @@ dword dworigin dwrite dxgi +dxgiformat +dxguid dynamiclock EABF EAC @@ -602,6 +621,7 @@ efa efgh EFile egistry +elif elseif emailandaccounts Emoji @@ -610,6 +630,7 @@ ENABLEDPOPUP endforeach endif endl +endpointvolume endregion Enque ENTERSIZEMOVE @@ -662,7 +683,7 @@ exlist EXPCMDFLAGS EXPCMDSTATE explr -Expr +expr exsb EXSEL exstyle @@ -679,6 +700,7 @@ FANCYZONESDRAWLAYOUTTEST FANCYZONESEDITOR Farbraum FARPROC +fdw feimage ffcd FFDDDDDD @@ -690,13 +712,16 @@ FILEFLAGS FILEFLAGSMASK FILEOP FILEOS +filepath FILESUBTYPE FILESYSPATH filesystem FILETIME FILETYPE FILEVERSION +Filtergraph Filterkeyboard +Filterx finalizer findfast findmydevice @@ -705,6 +730,7 @@ FIXEDFILEINFO FLASHZONES FLASHZONESONQUICKSWITCH Fle +flt fluentui flyout fmtlib @@ -726,6 +752,7 @@ FTYPE FULLNAME fullscreen func +Functiondiscoverykeys fwlink fwrite fxcop @@ -736,6 +763,7 @@ Gamebar gamedvr gamemode GBs +GCLP gcnew gdi gdiplus @@ -778,8 +806,10 @@ Hashset hbitmap hbmp hbr +HBRBACKGROUND HBRUSH hcblack +HCERTSTORE hcwhite hdc HDF @@ -791,6 +821,8 @@ hdrop HDS HEB helptext +HEVC +hfile HGLOBAL hhk HHmmss @@ -865,11 +897,13 @@ IApp IApplication IAppx IAsync +IAudio IAuto IBackground IBase IBeam IBind +ICapture icase iccex ICEBLUE @@ -922,9 +956,11 @@ IFancy ifdef IFeatures IFile +IFilter ifndef IFolder ifstream +IGraph iid IImage Iindex @@ -944,8 +980,11 @@ imagingdevices IMain IMarkdown ime +IMedia +IMem imeutil img +iminstall IMoniker IMonitor IMouse @@ -1002,6 +1041,7 @@ IObject iobjectwithsitesetsite IOle iolewindowcontextsensitivehelp +iomanip iostream IPackage IPath @@ -1023,6 +1063,7 @@ IProperty IPublic IQuery IRead +IReference IReflect IRegistered IRegistration @@ -1055,6 +1096,7 @@ ith IThrottled IThumbnail ITrigger +itsme IUI IUnknown IUri @@ -1065,9 +1107,11 @@ IVector IView IVirtual IWeb -IXml +IWIC IWindows +IXml ixx +IYUV IZone IZoom JArray @@ -1116,17 +1160,17 @@ Keytool keyup KILLFOCUS Knownfolders +KSPROPERTY Kybd LAlt Lambson lamotile langword -langword Lastdevice LASTEXITCODE -Laute launchfaceenrollment launchfingerprintenrollment +Laute laute laviusmotileng LAYOUTRTL @@ -1140,9 +1184,11 @@ Lclean LCONTROL LCtrl Ldone +ldx LEFTSCROLLBAR lego len +LEQ LError Lessthan LEVELID @@ -1166,6 +1212,7 @@ LINQTo Linux listbox listview +lld llkhf Llvm lmcons @@ -1246,6 +1293,7 @@ MAINICON Mainwindow majortype makeappx +makecab MAKEINTRESOURCE MAKEINTRESOURCEW MAKELPARAM @@ -1264,6 +1312,7 @@ MATCHMODE MAXIMIZEBOX MAXSHORTCUTSIZE maxversiontested +MBs MBUTTON MBUTTONDBLCLK MBUTTONDOWN @@ -1273,7 +1322,7 @@ MDICHILD MDL mdpreviewhandler MEDIASUBTYPE -MEDIATYPE +mediatype Melman memcpy memset @@ -1285,8 +1334,17 @@ messageboxes METACHARSET metadata metafile +mfapi mfc mfcribbon +mfidl +mfobjects +mfplat +mfreadwrite +Mfsensorgroup +mftransform +mfuuid +mic microsoft Midl mii @@ -1299,10 +1357,12 @@ miniz minlevel MINMAXINFO Miracast +mirophone MJPG mkdir Mlcfg MLogo +mmdeviceapi MMI Mmsys mobilehotspot @@ -1369,6 +1429,7 @@ mutex mutexes muxc mvvm +myfile MYICON NAMECHANGE nameof @@ -1412,6 +1473,7 @@ netsh netstandard Neue newcolor +newdev newitem newpath newrow @@ -1421,6 +1483,7 @@ niels nielslaute NIF nightlight +nitroin NLD nlog NLSTEXT @@ -1438,6 +1501,7 @@ nodoc noexcept NOFRAMES NOINHERITLAYOUT +NOINTERFACE NOLINKINFO NOMINMAX NOMOVE @@ -1521,6 +1585,7 @@ OPTIMIZEFORINVOKE optin optionalfeatures OPTIONSGROUP +ORAW ORPHANEDDIALOGTITLE oss ostr @@ -1532,6 +1597,7 @@ otheroptions otherusers OUTOFCONTEXT OUTOFMEMORY +outpin Outptr outputtype outro @@ -1553,6 +1619,7 @@ PARENTRELATIVEPARSING parray PARTIALCONFIRMATIONDIALOGTITLE pathcch +PAUDIO pbc Pbgra pcb @@ -1593,6 +1660,7 @@ Pipelinhttps pipename pitem PKBDLLHOOKSTRUCT +PKEY placeholders plib PLK @@ -1611,6 +1679,7 @@ popd popup POPUPWINDOW posix +Postion powerappscds powercfg powerlauncher @@ -1626,6 +1695,7 @@ powertoyswiki Powrprof ppenum ppidl +ppmt pprm pproc ppsi @@ -1643,11 +1713,13 @@ Prefixer Preinstalled preload PREMULTIPLIED +preperty prevhost previewer PREVIEWGROUP PREVIEWHANDLERFRAMEINFO previewpane +previouscamera PREVIOUSVERSIONSINSTALLED prevpane prgms @@ -1664,6 +1736,7 @@ PROGRAMFILES progressbar Proj projectname +PROPBAG propkey propvarutil prpui @@ -1676,6 +1749,7 @@ psfgao Psr psrm psrree +pstr pstream pstrm psz @@ -1693,6 +1767,7 @@ PVOID pwa pwcs PWSTR +pwsz pwtd qianlifeng qit @@ -1738,20 +1813,27 @@ rectp rects recyclebin redirectedfrom +reencode +reencoded refactor refactoring REFCLSID refcount +REFGUID REFIID REGCLS regedit regex +REGFILTER +REGFILTERPINS regionformatting regionlanguage REGISTERCLASSFAILED Registery registrypath regkey +REGPINTYPES +regsvr reimplementing reloadable Remapper @@ -1803,13 +1885,14 @@ RKey RMENU RNumber roadmap +robocopy Roboto roslyn royvou Rpc RRF -rshift RSHIFT +rshift Rsp rst Rstrtmgr @@ -1859,6 +1942,7 @@ SEARCHFOR SEARCHREPLACEGROUP searchterm Secur +seekg Segoe Sekan SENDCHANGE @@ -1904,8 +1988,8 @@ Shl shldisp shlobj shlwapi +shmem shobjidl -shortsplit SHORTCUTATLEAST shortcutcontrol Shortcutguide @@ -1917,6 +2001,7 @@ SHORTCUTSTARTWITHMODIFIER Shortcuttool shortdate SHORTPATH +shortsplit showcolorname SHOWDEFAULT SHOWELEVATIONPROMPT @@ -1945,6 +2030,7 @@ SIZENESW SIZENS SIZENWSE sizeof +sizeread SIZEWE sketchapp SKIPOWNPROCESS @@ -1974,6 +2060,7 @@ spesi splitwstring sppd sppre +sprintf spsi spsia spsrif @@ -2031,6 +2118,7 @@ storagesense stoul stoull strcmp +streampos strftime Stringified Stringify @@ -2057,8 +2145,8 @@ surfacehub sut SVE svg -SVGIO SVGIn +SVGIO svgpreviewhandler SWC SWFO @@ -2068,6 +2156,7 @@ swprintf SWRESTORE SYMED SYMOPT +SYNCMFT SYNCPAINT sys SYSCHAR @@ -2083,9 +2172,9 @@ syslog SYSMENU systemd SYSTEMTIME -Tadele sz tabletmode +Tadele tadele Tahoma talenthrcore @@ -2111,6 +2200,7 @@ tchar tcscpy TCustom TDevice +tellg Templated templatenamespace Temporarilypeekatthedesktop @@ -2132,8 +2222,8 @@ THISCOMPONENT thre tif TILEDWINDOW -timediff timedate +timediff Timeline TIMERID timeunion @@ -2143,6 +2233,7 @@ TLayout tlb tlbimp tmp +TMPVAR TNP todo toggleswitch @@ -2161,12 +2252,15 @@ towlower towupper tracelogging traies +transcoded +transparrent TRAYMOUSEMESSAGE TRK trl trueplay truetype trunc +tspan TStr tsx TYMED @@ -2192,8 +2286,10 @@ UIPI UIs UITo ULARGE +ulazy ULLONG ulong +ULONGLONG umd unchecks uncomment @@ -2222,6 +2318,7 @@ unknwn UNLEN unlicense Unmap +unmute UNORM unregister unregistering @@ -2256,16 +2353,20 @@ uuidof uwp UWPUI uxtheme +UYVY validmodulename vcamp vccorlib +vcdl VCINSTALLDIR +vcm vcomp vcredist VCRT vcruntime vcvars vcxproj +vdi VDId vec VERBSONLY @@ -2275,10 +2376,14 @@ VERSIONINFO Versioning VFT vid +VIDCAP +videoconference +videoconferencevirtualdriver VIDEOINFOHEADER videoplayback viewbox viewmodel +vih virtualization visiblecolorformats Visibletrue @@ -2298,6 +2403,7 @@ VSCBD vscode VSCROLL vse +vsix vsonline vstemplate VSTHRD @@ -2320,6 +2426,9 @@ wcscpy wcslen wcsncmp wcsnicmp +WDK +wdksetup +wdkvsix wdp wdupenv We'd @@ -2346,6 +2455,8 @@ wikipedia wil wildcards winapi +wincodec +Wincodecsdk wincolor windef windevbuildagents @@ -2401,6 +2512,7 @@ WMKEYUP wmp WMSYSKEYDOWN WMSYSKEYUP +WMV wnd WNDCLASS WNDCLASSEX @@ -2431,9 +2543,11 @@ wstringstream wsz wtoi WTS +wtsapi WTSAT wu wubi +WVC Wwan www wxs @@ -2448,6 +2562,7 @@ XBUTTON XBUTTONDBLCLK XBUTTONDOWN XBUTTONUP +xcopy XDiff XDocument XElement @@ -2479,7 +2594,10 @@ yourinfo YourUserName YStr YUY +yuyoyuppe YUYV +YVU +YVYU ZEROINIT ZIndex zipfolder @@ -2491,9 +2609,3 @@ ZONESETCHANGE Zoneszonabletester Zoomusingmagnifier zzz -coc -djsoref -dogancelik -itsme -nitroin -ulazy diff --git a/.pipelines/build-tools.cmd b/.pipelines/build-tools.cmd index 6ae68dbed1..aa5f782932 100644 --- a/.pipelines/build-tools.cmd +++ b/.pipelines/build-tools.cmd @@ -7,3 +7,5 @@ set SolutionDir=%cd% popd SET IsPipeline=1 call msbuild ../tools/BugReportTool/BugReportTool.sln /p:Configuration=Release /p:Platform=x64 /p:CIBuild=true || exit /b 1 +call msbuild ../tools/WebcamReportTool/WebcamReportTool.sln /p:Configuration=Release /p:Platform=x64 /p:CIBuild=true || exit /b 1 + diff --git a/.pipelines/pipeline.user.windows.yml b/.pipelines/pipeline.user.windows.yml index 5898c1db0b..29581b3763 100644 --- a/.pipelines/pipeline.user.windows.yml +++ b/.pipelines/pipeline.user.windows.yml @@ -61,6 +61,11 @@ build: - 'x64/**/*.pdb' exclude: - 'x64/Release/obj/**/*.pdb' + # TODO(yuyoyuppe): uncomment when VCM should be enabled + #- from: 'x86/Release' + # to: 'Build_Output' + # include: + # - 'modules\VideoConference\VideoConferenceProxyFilter_x86.dll' - from: 'x64/Release' to: 'Build_Output' include: @@ -159,6 +164,9 @@ build: - 'modules\PowerRename\PowerRenameExt.dll' - 'modules\ShortcutGuide\ShortcutGuide\PowerToys.ShortcutGuide.exe' - 'modules\ShortcutGuide\ShortcutGuideModuleInterface\ShortcutGuideModuleInterface.dll' + # TODO(yuyoyuppe): uncomment when VCM should be enabled + #- 'modules\VideoConference\VideoConferenceModule.dll' + #- 'modules\VideoConference\VideoConferenceProxyFilter_x64.dll' - 'Settings\ManagedTelemetry.dll' - 'Settings\Microsoft.PowerToys.Settings.UI.exe' - 'Settings\Microsoft.PowerToys.Settings.UI.Lib.dll' @@ -177,6 +185,7 @@ build: to: 'Build_Output' include: - 'BugReportTool\BugReportTool.exe' + - 'WebcamReportTool\WebcamReportTool.exe' signing_options: sign_inline: true # This does signing a soon as this command completes - !!buildcommand @@ -237,4 +246,3 @@ static_analysis_options: files_to_scan: - exclude: - '**/*.lcl' - diff --git a/.pipelines/restore-tools.cmd b/.pipelines/restore-tools.cmd index 852d59942d..0f374f9b59 100644 --- a/.pipelines/restore-tools.cmd +++ b/.pipelines/restore-tools.cmd @@ -1,3 +1,4 @@ cd /D "%~dp0" nuget restore ../tools/BugReportTool/BugReportTool.sln || exit /b 1 +nuget restore ../tools/WebcamReportTool/WebcamReportTool.sln || exit /b 1 diff --git a/.pipelines/restore.cmd b/.pipelines/restore.cmd index fd630aea59..22eb393aaa 100644 --- a/.pipelines/restore.cmd +++ b/.pipelines/restore.cmd @@ -1,3 +1,12 @@ cd /D "%~dp0" nuget restore ../PowerToys.sln || exit /b 1 + +powershell.exe -Command "Invoke-WebRequest -OutFile %tmp%\wdksetup.exe https://go.microsoft.com/fwlink/p/?linkid=2085767" +%tmp%\wdksetup.exe /q + +copy "C:\Program Files (x86)\Windows Kits\10\Vsix\VS2019\WDK.vsix" %tmp%\wdkvsix.zip +powershell Expand-Archive %tmp%\wdkvsix.zip -DestinationPath %tmp%\wdkvsix -Force + +robocopy /e %tmp%\wdkvsix\$MSBuild\Microsoft\VC\v160 "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\MSBuild\Microsoft\VC\v160" || IF %ERRORLEVEL% LEQ 7 EXIT 0 +robocopy /e %tmp%\wdkvsix\$VCTargets "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\VC\VCTargets" || IF %ERRORLEVEL% LEQ 7 EXIT 0 diff --git a/Cpp.Build.props b/Cpp.Build.props index ed8209a0ac..c6d115cd13 100644 --- a/Cpp.Build.props +++ b/Cpp.Build.props @@ -75,7 +75,7 @@ - v142 + v142 $(SolutionDir)$(Platform)\$(Configuration)\obj\$(ProjectName)\ Unicode Spectre diff --git a/PowerToys.sln b/PowerToys.sln index f4a534638c..4d0c415207 100644 --- a/PowerToys.sln +++ b/PowerToys.sln @@ -333,7 +333,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Community.PowerToys.Run.Plu EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Community.PowerToys.Run.Plugin.UnitConverter.UnitTest", "src\modules\launcher\Plugins\Community.PowerToys.Run.Plugin.UnitConverter.UnitTest\Community.PowerToys.Run.Plugin.UnitConverter.UnitTest.csproj", "{3E424AD2-19E5-4AE6-B833-F53963EB5FC1}" EndProject -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "shortcutguide", "shortcutguide", "{106CBECA-0701-4FC3-838C-9DF816A19AE2}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ShortcutGuideModuleInterface", "src\modules\ShortcutGuide\ShortcutGuideModuleInterface\ShortcutGuideModuleInterface.vcxproj", "{2D604C07-51FC-46BB-9EB7-75AECC7F5E81}" @@ -342,376 +341,597 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ShortcutGuide", "src\module EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "FancyZonesModuleInterface", "src\modules\fancyzones\FancyZonesModuleInterface\FancyZonesModuleInterface.vcxproj", "{48804216-2A0E-4168-A6D8-9CD068D14227}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "FancyZones", "src\modules\fancyzones\FancyZones\FancyZones.vcxproj", "{390AE700-B55F-4202-91EA-A822EB75B9BD}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "FancyZones", "src\modules\fancyzones\FancyZones\FancyZones.vcxproj", "{FF1D7936-842A-4BBB-8BEA-E9FE796DE700}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PowerToys.Update", "src\Update\PowerToys.Update.vcxproj", "{44CE9AE1-4390-42C5-BACC-0FD6B40AA203}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.PowerToys.Run.Plugin.WindowsSettings", "src\modules\launcher\Plugins\Microsoft.PowerToys.Run.Plugin.WindowsSettings\Microsoft.PowerToys.Run.Plugin.WindowsSettings.csproj", "{5043CECE-E6A7-4867-9CBE-02D27D83747A}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "VideoConferenceShared", "src\modules\videoconference\VideoConferenceShared\VideoConferenceShared.vcxproj", "{459E0768-7EBD-4C41-BBA1-6DB3B3815E0A}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "VideoConferenceModule", "src\modules\videoconference\VideoConferenceModule\Video Conference.vcxproj", "{5ABA70DE-3A3F-41F6-A1F5-D1F74F54F9BB}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "VideoConferenceProxyFilter", "src\modules\videoconference\VideoConferenceProxyFilter\VideoConferenceProxyFilter.vcxproj", "{AC2857B4-103D-4D6D-9740-926EBF785042}" + ProjectSection(ProjectDependencies) = postProject + {459E0768-7EBD-4C41-BBA1-6DB3B3815E0A} = {459E0768-7EBD-4C41-BBA1-6DB3B3815E0A} + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "VideoConference", "VideoConference", "{470FBAF9-E1F8-4F3E-8786-198A1C81C8A8}" +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 {9412D5C6-2CF2-4FC2-A601-B55508EA9B27}.Debug|x64.ActiveCfg = Debug|x64 {9412D5C6-2CF2-4FC2-A601-B55508EA9B27}.Debug|x64.Build.0 = Debug|x64 + {9412D5C6-2CF2-4FC2-A601-B55508EA9B27}.Debug|x86.ActiveCfg = Debug|x64 {9412D5C6-2CF2-4FC2-A601-B55508EA9B27}.Release|x64.ActiveCfg = Release|x64 {9412D5C6-2CF2-4FC2-A601-B55508EA9B27}.Release|x64.Build.0 = Release|x64 + {9412D5C6-2CF2-4FC2-A601-B55508EA9B27}.Release|x86.ActiveCfg = Release|x64 {F9C68EDF-AC74-4B77-9AF1-005D9C9F6A99}.Debug|x64.ActiveCfg = Debug|x64 {F9C68EDF-AC74-4B77-9AF1-005D9C9F6A99}.Debug|x64.Build.0 = Debug|x64 + {F9C68EDF-AC74-4B77-9AF1-005D9C9F6A99}.Debug|x86.ActiveCfg = Debug|x64 {F9C68EDF-AC74-4B77-9AF1-005D9C9F6A99}.Release|x64.ActiveCfg = Release|x64 {F9C68EDF-AC74-4B77-9AF1-005D9C9F6A99}.Release|x64.Build.0 = Release|x64 + {F9C68EDF-AC74-4B77-9AF1-005D9C9F6A99}.Release|x86.ActiveCfg = Release|x64 {9C6A7905-72D4-4BF5-B256-ABFDAEF68AE9}.Debug|x64.ActiveCfg = Debug|x64 {9C6A7905-72D4-4BF5-B256-ABFDAEF68AE9}.Debug|x64.Build.0 = Debug|x64 + {9C6A7905-72D4-4BF5-B256-ABFDAEF68AE9}.Debug|x86.ActiveCfg = Debug|x64 {9C6A7905-72D4-4BF5-B256-ABFDAEF68AE9}.Release|x64.ActiveCfg = Release|x64 {9C6A7905-72D4-4BF5-B256-ABFDAEF68AE9}.Release|x64.Build.0 = Release|x64 + {9C6A7905-72D4-4BF5-B256-ABFDAEF68AE9}.Release|x86.ActiveCfg = Release|x64 {1A066C63-64B3-45F8-92FE-664E1CCE8077}.Debug|x64.ActiveCfg = Debug|x64 {1A066C63-64B3-45F8-92FE-664E1CCE8077}.Debug|x64.Build.0 = Debug|x64 + {1A066C63-64B3-45F8-92FE-664E1CCE8077}.Debug|x86.ActiveCfg = Debug|x64 {1A066C63-64B3-45F8-92FE-664E1CCE8077}.Release|x64.ActiveCfg = Release|x64 {1A066C63-64B3-45F8-92FE-664E1CCE8077}.Release|x64.Build.0 = Release|x64 + {1A066C63-64B3-45F8-92FE-664E1CCE8077}.Release|x86.ActiveCfg = Release|x64 {5CCC8468-DEC8-4D36-99D4-5C891BEBD481}.Debug|x64.ActiveCfg = Debug|x64 {5CCC8468-DEC8-4D36-99D4-5C891BEBD481}.Debug|x64.Build.0 = Debug|x64 + {5CCC8468-DEC8-4D36-99D4-5C891BEBD481}.Debug|x86.ActiveCfg = Debug|x64 {5CCC8468-DEC8-4D36-99D4-5C891BEBD481}.Release|x64.ActiveCfg = Release|x64 {5CCC8468-DEC8-4D36-99D4-5C891BEBD481}.Release|x64.Build.0 = Release|x64 + {5CCC8468-DEC8-4D36-99D4-5C891BEBD481}.Release|x86.ActiveCfg = Release|x64 {B25AC7A5-FB9F-4789-B392-D5C85E948670}.Debug|x64.ActiveCfg = Debug|x64 {B25AC7A5-FB9F-4789-B392-D5C85E948670}.Debug|x64.Build.0 = Debug|x64 + {B25AC7A5-FB9F-4789-B392-D5C85E948670}.Debug|x86.ActiveCfg = Debug|x64 {B25AC7A5-FB9F-4789-B392-D5C85E948670}.Release|x64.ActiveCfg = Release|x64 {B25AC7A5-FB9F-4789-B392-D5C85E948670}.Release|x64.Build.0 = Release|x64 + {B25AC7A5-FB9F-4789-B392-D5C85E948670}.Release|x86.ActiveCfg = Release|x64 {51920F1F-C28C-4ADF-8660-4238766796C2}.Debug|x64.ActiveCfg = Debug|x64 {51920F1F-C28C-4ADF-8660-4238766796C2}.Debug|x64.Build.0 = Debug|x64 + {51920F1F-C28C-4ADF-8660-4238766796C2}.Debug|x86.ActiveCfg = Debug|x64 {51920F1F-C28C-4ADF-8660-4238766796C2}.Release|x64.ActiveCfg = Release|x64 {51920F1F-C28C-4ADF-8660-4238766796C2}.Release|x64.Build.0 = Release|x64 + {51920F1F-C28C-4ADF-8660-4238766796C2}.Release|x86.ActiveCfg = Release|x64 {0E072714-D127-460B-AFAD-B4C40B412798}.Debug|x64.ActiveCfg = Debug|x64 {0E072714-D127-460B-AFAD-B4C40B412798}.Debug|x64.Build.0 = Debug|x64 + {0E072714-D127-460B-AFAD-B4C40B412798}.Debug|x86.ActiveCfg = Debug|x64 {0E072714-D127-460B-AFAD-B4C40B412798}.Release|x64.ActiveCfg = Release|x64 {0E072714-D127-460B-AFAD-B4C40B412798}.Release|x64.Build.0 = Release|x64 + {0E072714-D127-460B-AFAD-B4C40B412798}.Release|x86.ActiveCfg = Release|x64 {A3935CF4-46C5-4A88-84D3-6B12E16E6BA2}.Debug|x64.ActiveCfg = Debug|x64 {A3935CF4-46C5-4A88-84D3-6B12E16E6BA2}.Debug|x64.Build.0 = Debug|x64 + {A3935CF4-46C5-4A88-84D3-6B12E16E6BA2}.Debug|x86.ActiveCfg = Debug|x64 {A3935CF4-46C5-4A88-84D3-6B12E16E6BA2}.Release|x64.ActiveCfg = Release|x64 {A3935CF4-46C5-4A88-84D3-6B12E16E6BA2}.Release|x64.Build.0 = Release|x64 + {A3935CF4-46C5-4A88-84D3-6B12E16E6BA2}.Release|x86.ActiveCfg = Release|x64 {2151F984-E006-4A9F-92EF-C6DDE3DC8413}.Debug|x64.ActiveCfg = Debug|x64 {2151F984-E006-4A9F-92EF-C6DDE3DC8413}.Debug|x64.Build.0 = Debug|x64 + {2151F984-E006-4A9F-92EF-C6DDE3DC8413}.Debug|x86.ActiveCfg = Debug|x64 {2151F984-E006-4A9F-92EF-C6DDE3DC8413}.Release|x64.ActiveCfg = Release|x64 {2151F984-E006-4A9F-92EF-C6DDE3DC8413}.Release|x64.Build.0 = Release|x64 + {2151F984-E006-4A9F-92EF-C6DDE3DC8413}.Release|x86.ActiveCfg = Release|x64 {64A80062-4D8B-4229-8A38-DFA1D7497749}.Debug|x64.ActiveCfg = Debug|x64 {64A80062-4D8B-4229-8A38-DFA1D7497749}.Debug|x64.Build.0 = Debug|x64 + {64A80062-4D8B-4229-8A38-DFA1D7497749}.Debug|x86.ActiveCfg = Debug|x64 {64A80062-4D8B-4229-8A38-DFA1D7497749}.Release|x64.ActiveCfg = Release|x64 {64A80062-4D8B-4229-8A38-DFA1D7497749}.Release|x64.Build.0 = Release|x64 + {64A80062-4D8B-4229-8A38-DFA1D7497749}.Release|x86.ActiveCfg = Release|x64 {0485F45C-EA7A-4BB5-804B-3E8D14699387}.Debug|x64.ActiveCfg = Debug|x64 + {0485F45C-EA7A-4BB5-804B-3E8D14699387}.Debug|x64.Build.0 = Debug|x64 + {0485F45C-EA7A-4BB5-804B-3E8D14699387}.Debug|x86.ActiveCfg = Debug|x64 {0485F45C-EA7A-4BB5-804B-3E8D14699387}.Release|x64.ActiveCfg = Release|x64 + {0485F45C-EA7A-4BB5-804B-3E8D14699387}.Release|x64.Build.0 = Release|x64 + {0485F45C-EA7A-4BB5-804B-3E8D14699387}.Release|x86.ActiveCfg = Release|x64 {89F34AF7-1C34-4A72-AA6E-534BCF972BD9}.Debug|x64.ActiveCfg = Debug|x64 {89F34AF7-1C34-4A72-AA6E-534BCF972BD9}.Debug|x64.Build.0 = Debug|x64 + {89F34AF7-1C34-4A72-AA6E-534BCF972BD9}.Debug|x86.ActiveCfg = Debug|x64 {89F34AF7-1C34-4A72-AA6E-534BCF972BD9}.Release|x64.ActiveCfg = Release|x64 {89F34AF7-1C34-4A72-AA6E-534BCF972BD9}.Release|x64.Build.0 = Release|x64 + {89F34AF7-1C34-4A72-AA6E-534BCF972BD9}.Release|x86.ActiveCfg = Release|x64 {2BE46397-4DFA-414C-9BD4-41E4BBF8CB34}.Debug|x64.ActiveCfg = Debug|x64 {2BE46397-4DFA-414C-9BD4-41E4BBF8CB34}.Debug|x64.Build.0 = Debug|x64 + {2BE46397-4DFA-414C-9BD4-41E4BBF8CB34}.Debug|x86.ActiveCfg = Debug|x64 {2BE46397-4DFA-414C-9BD4-41E4BBF8CB34}.Release|x64.ActiveCfg = Release|x64 {2BE46397-4DFA-414C-9BD4-41E4BBF8CB34}.Release|x64.Build.0 = Release|x64 + {2BE46397-4DFA-414C-9BD4-41E4BBF8CB34}.Release|x86.ActiveCfg = Release|x64 {0B43679E-EDFA-4DA0-AD30-F4628B308B1B}.Debug|x64.ActiveCfg = Debug|x64 {0B43679E-EDFA-4DA0-AD30-F4628B308B1B}.Debug|x64.Build.0 = Debug|x64 + {0B43679E-EDFA-4DA0-AD30-F4628B308B1B}.Debug|x86.ActiveCfg = Debug|x64 {0B43679E-EDFA-4DA0-AD30-F4628B308B1B}.Release|x64.ActiveCfg = Release|x64 {0B43679E-EDFA-4DA0-AD30-F4628B308B1B}.Release|x64.Build.0 = Release|x64 + {0B43679E-EDFA-4DA0-AD30-F4628B308B1B}.Release|x86.ActiveCfg = Release|x64 {E0CC7526-D85E-43AC-844F-D5DF0D2F5AB8}.Debug|x64.ActiveCfg = Debug|x64 {E0CC7526-D85E-43AC-844F-D5DF0D2F5AB8}.Debug|x64.Build.0 = Debug|x64 + {E0CC7526-D85E-43AC-844F-D5DF0D2F5AB8}.Debug|x86.ActiveCfg = Debug|x64 {E0CC7526-D85E-43AC-844F-D5DF0D2F5AB8}.Release|x64.ActiveCfg = Release|x64 {E0CC7526-D85E-43AC-844F-D5DF0D2F5AB8}.Release|x64.Build.0 = Release|x64 + {E0CC7526-D85E-43AC-844F-D5DF0D2F5AB8}.Release|x86.ActiveCfg = Release|x64 {D29DDD63-E2CF-4657-9FD5-2AEDE4257E5D}.Debug|x64.ActiveCfg = Debug|x64 {D29DDD63-E2CF-4657-9FD5-2AEDE4257E5D}.Debug|x64.Build.0 = Debug|x64 + {D29DDD63-E2CF-4657-9FD5-2AEDE4257E5D}.Debug|x86.ActiveCfg = Debug|x64 {D29DDD63-E2CF-4657-9FD5-2AEDE4257E5D}.Release|x64.ActiveCfg = Release|x64 {D29DDD63-E2CF-4657-9FD5-2AEDE4257E5D}.Release|x64.Build.0 = Release|x64 + {D29DDD63-E2CF-4657-9FD5-2AEDE4257E5D}.Release|x86.ActiveCfg = Release|x64 {17DA04DF-E393-4397-9CF0-84DABE11032E}.Debug|x64.ActiveCfg = Debug|x64 {17DA04DF-E393-4397-9CF0-84DABE11032E}.Debug|x64.Build.0 = Debug|x64 + {17DA04DF-E393-4397-9CF0-84DABE11032E}.Debug|x86.ActiveCfg = Debug|x64 {17DA04DF-E393-4397-9CF0-84DABE11032E}.Release|x64.ActiveCfg = Release|x64 {17DA04DF-E393-4397-9CF0-84DABE11032E}.Release|x64.Build.0 = Release|x64 + {17DA04DF-E393-4397-9CF0-84DABE11032E}.Release|x86.ActiveCfg = Release|x64 {8AFFA899-0B73-49EC-8C50-0FADDA57B2FC}.Debug|x64.ActiveCfg = Debug|x64 {8AFFA899-0B73-49EC-8C50-0FADDA57B2FC}.Debug|x64.Build.0 = Debug|x64 + {8AFFA899-0B73-49EC-8C50-0FADDA57B2FC}.Debug|x86.ActiveCfg = Debug|x64 {8AFFA899-0B73-49EC-8C50-0FADDA57B2FC}.Release|x64.ActiveCfg = Release|x64 {8AFFA899-0B73-49EC-8C50-0FADDA57B2FC}.Release|x64.Build.0 = Release|x64 + {8AFFA899-0B73-49EC-8C50-0FADDA57B2FC}.Release|x86.ActiveCfg = Release|x64 {4FD29318-A8AB-4D8F-AA47-60BC241B8DA3}.Debug|x64.ActiveCfg = Debug|x64 {4FD29318-A8AB-4D8F-AA47-60BC241B8DA3}.Debug|x64.Build.0 = Debug|x64 + {4FD29318-A8AB-4D8F-AA47-60BC241B8DA3}.Debug|x86.ActiveCfg = Debug|x64 {4FD29318-A8AB-4D8F-AA47-60BC241B8DA3}.Release|x64.ActiveCfg = Release|x64 {4FD29318-A8AB-4D8F-AA47-60BC241B8DA3}.Release|x64.Build.0 = Release|x64 + {4FD29318-A8AB-4D8F-AA47-60BC241B8DA3}.Release|x86.ActiveCfg = Release|x64 {8451ECDD-2EA4-4966-BB0A-7BBC40138E80}.Debug|x64.ActiveCfg = Debug|x64 {8451ECDD-2EA4-4966-BB0A-7BBC40138E80}.Debug|x64.Build.0 = Debug|x64 + {8451ECDD-2EA4-4966-BB0A-7BBC40138E80}.Debug|x86.ActiveCfg = Debug|x64 {8451ECDD-2EA4-4966-BB0A-7BBC40138E80}.Release|x64.ActiveCfg = Release|x64 {8451ECDD-2EA4-4966-BB0A-7BBC40138E80}.Release|x64.Build.0 = Release|x64 + {8451ECDD-2EA4-4966-BB0A-7BBC40138E80}.Release|x86.ActiveCfg = Release|x64 {FF742965-9A80-41A5-B042-D6C7D3A21708}.Debug|x64.ActiveCfg = Debug|x64 {FF742965-9A80-41A5-B042-D6C7D3A21708}.Debug|x64.Build.0 = Debug|x64 + {FF742965-9A80-41A5-B042-D6C7D3A21708}.Debug|x86.ActiveCfg = Debug|x64 {FF742965-9A80-41A5-B042-D6C7D3A21708}.Release|x64.ActiveCfg = Release|x64 {FF742965-9A80-41A5-B042-D6C7D3A21708}.Release|x64.Build.0 = Release|x64 + {FF742965-9A80-41A5-B042-D6C7D3A21708}.Release|x86.ActiveCfg = Release|x64 {59BD9891-3837-438A-958D-ADC7F91F6F7E}.Debug|x64.ActiveCfg = Debug|x64 {59BD9891-3837-438A-958D-ADC7F91F6F7E}.Debug|x64.Build.0 = Debug|x64 + {59BD9891-3837-438A-958D-ADC7F91F6F7E}.Debug|x86.ActiveCfg = Debug|x64 {59BD9891-3837-438A-958D-ADC7F91F6F7E}.Release|x64.ActiveCfg = Release|x64 {59BD9891-3837-438A-958D-ADC7F91F6F7E}.Release|x64.Build.0 = Release|x64 + {59BD9891-3837-438A-958D-ADC7F91F6F7E}.Release|x86.ActiveCfg = Release|x64 {4D971245-7A70-41D5-BAA0-DDB5684CAF51}.Debug|x64.ActiveCfg = Debug|x64 {4D971245-7A70-41D5-BAA0-DDB5684CAF51}.Debug|x64.Build.0 = Debug|x64 + {4D971245-7A70-41D5-BAA0-DDB5684CAF51}.Debug|x86.ActiveCfg = Debug|x64 {4D971245-7A70-41D5-BAA0-DDB5684CAF51}.Release|x64.ActiveCfg = Release|x64 {4D971245-7A70-41D5-BAA0-DDB5684CAF51}.Release|x64.Build.0 = Release|x64 + {4D971245-7A70-41D5-BAA0-DDB5684CAF51}.Release|x86.ActiveCfg = Release|x64 {74F1B9ED-F59C-4FE7-B473-7B453E30837E}.Debug|x64.ActiveCfg = Debug|x64 {74F1B9ED-F59C-4FE7-B473-7B453E30837E}.Debug|x64.Build.0 = Debug|x64 + {74F1B9ED-F59C-4FE7-B473-7B453E30837E}.Debug|x86.ActiveCfg = Debug|x64 {74F1B9ED-F59C-4FE7-B473-7B453E30837E}.Release|x64.ActiveCfg = Release|x64 {74F1B9ED-F59C-4FE7-B473-7B453E30837E}.Release|x64.Build.0 = Release|x64 + {74F1B9ED-F59C-4FE7-B473-7B453E30837E}.Release|x86.ActiveCfg = Release|x64 {FDB3555B-58EF-4AE6-B5F1-904719637AB4}.Debug|x64.ActiveCfg = Debug|x64 {FDB3555B-58EF-4AE6-B5F1-904719637AB4}.Debug|x64.Build.0 = Debug|x64 + {FDB3555B-58EF-4AE6-B5F1-904719637AB4}.Debug|x86.ActiveCfg = Debug|x64 {FDB3555B-58EF-4AE6-B5F1-904719637AB4}.Release|x64.ActiveCfg = Release|x64 {FDB3555B-58EF-4AE6-B5F1-904719637AB4}.Release|x64.Build.0 = Release|x64 + {FDB3555B-58EF-4AE6-B5F1-904719637AB4}.Release|x86.ActiveCfg = Release|x64 {C21BFF9C-2C99-4B5F-B7C9-A5E6DDDB37B0}.Debug|x64.ActiveCfg = Debug|x64 {C21BFF9C-2C99-4B5F-B7C9-A5E6DDDB37B0}.Debug|x64.Build.0 = Debug|x64 + {C21BFF9C-2C99-4B5F-B7C9-A5E6DDDB37B0}.Debug|x86.ActiveCfg = Debug|x64 {C21BFF9C-2C99-4B5F-B7C9-A5E6DDDB37B0}.Release|x64.ActiveCfg = Release|x64 {C21BFF9C-2C99-4B5F-B7C9-A5E6DDDB37B0}.Release|x64.Build.0 = Release|x64 + {C21BFF9C-2C99-4B5F-B7C9-A5E6DDDB37B0}.Release|x86.ActiveCfg = Release|x64 {F8B870EB-D5F5-45BA-9CF7-A5C459818820}.Debug|x64.ActiveCfg = Debug|x64 {F8B870EB-D5F5-45BA-9CF7-A5C459818820}.Debug|x64.Build.0 = Debug|x64 + {F8B870EB-D5F5-45BA-9CF7-A5C459818820}.Debug|x86.ActiveCfg = Debug|x64 {F8B870EB-D5F5-45BA-9CF7-A5C459818820}.Release|x64.ActiveCfg = Release|x64 {F8B870EB-D5F5-45BA-9CF7-A5C459818820}.Release|x64.Build.0 = Release|x64 + {F8B870EB-D5F5-45BA-9CF7-A5C459818820}.Release|x86.ActiveCfg = Release|x64 {E364F67B-BB12-4E91-B639-355866EBCD8B}.Debug|x64.ActiveCfg = Debug|x64 {E364F67B-BB12-4E91-B639-355866EBCD8B}.Debug|x64.Build.0 = Debug|x64 + {E364F67B-BB12-4E91-B639-355866EBCD8B}.Debug|x86.ActiveCfg = Debug|x64 {E364F67B-BB12-4E91-B639-355866EBCD8B}.Release|x64.ActiveCfg = Release|x64 {E364F67B-BB12-4E91-B639-355866EBCD8B}.Release|x64.Build.0 = Release|x64 + {E364F67B-BB12-4E91-B639-355866EBCD8B}.Release|x86.ActiveCfg = Release|x64 {F97E5003-F263-4D4A-A964-0F1F3C82DEF2}.Debug|x64.ActiveCfg = Debug|x64 {F97E5003-F263-4D4A-A964-0F1F3C82DEF2}.Debug|x64.Build.0 = Debug|x64 + {F97E5003-F263-4D4A-A964-0F1F3C82DEF2}.Debug|x86.ActiveCfg = Debug|x64 {F97E5003-F263-4D4A-A964-0F1F3C82DEF2}.Release|x64.ActiveCfg = Release|x64 {F97E5003-F263-4D4A-A964-0F1F3C82DEF2}.Release|x64.Build.0 = Release|x64 + {F97E5003-F263-4D4A-A964-0F1F3C82DEF2}.Release|x86.ActiveCfg = Release|x64 {AF2349B8-E5B6-4004-9502-687C1C7730B1}.Debug|x64.ActiveCfg = Debug|x64 {AF2349B8-E5B6-4004-9502-687C1C7730B1}.Debug|x64.Build.0 = Debug|x64 + {AF2349B8-E5B6-4004-9502-687C1C7730B1}.Debug|x86.ActiveCfg = Debug|x64 {AF2349B8-E5B6-4004-9502-687C1C7730B1}.Release|x64.ActiveCfg = Release|x64 {AF2349B8-E5B6-4004-9502-687C1C7730B1}.Release|x64.Build.0 = Release|x64 + {AF2349B8-E5B6-4004-9502-687C1C7730B1}.Release|x86.ActiveCfg = Release|x64 {6A71162E-FC4C-4A2C-B90F-3CF94F59A9BB}.Debug|x64.ActiveCfg = Debug|x64 {6A71162E-FC4C-4A2C-B90F-3CF94F59A9BB}.Debug|x64.Build.0 = Debug|x64 + {6A71162E-FC4C-4A2C-B90F-3CF94F59A9BB}.Debug|x86.ActiveCfg = Debug|x64 {6A71162E-FC4C-4A2C-B90F-3CF94F59A9BB}.Release|x64.ActiveCfg = Release|x64 {6A71162E-FC4C-4A2C-B90F-3CF94F59A9BB}.Release|x64.Build.0 = Release|x64 + {6A71162E-FC4C-4A2C-B90F-3CF94F59A9BB}.Release|x86.ActiveCfg = Release|x64 {A2B51B8B-8F90-424E-BC97-F9AB7D76CA1A}.Debug|x64.ActiveCfg = Debug|x64 {A2B51B8B-8F90-424E-BC97-F9AB7D76CA1A}.Debug|x64.Build.0 = Debug|x64 + {A2B51B8B-8F90-424E-BC97-F9AB7D76CA1A}.Debug|x86.ActiveCfg = Debug|x64 {A2B51B8B-8F90-424E-BC97-F9AB7D76CA1A}.Release|x64.ActiveCfg = Release|x64 {A2B51B8B-8F90-424E-BC97-F9AB7D76CA1A}.Release|x64.Build.0 = Release|x64 + {A2B51B8B-8F90-424E-BC97-F9AB7D76CA1A}.Release|x86.ActiveCfg = Release|x64 {DA425894-6E13-404F-8DCB-78584EC0557A}.Debug|x64.ActiveCfg = Debug|x64 {DA425894-6E13-404F-8DCB-78584EC0557A}.Debug|x64.Build.0 = Debug|x64 + {DA425894-6E13-404F-8DCB-78584EC0557A}.Debug|x86.ActiveCfg = Debug|x64 {DA425894-6E13-404F-8DCB-78584EC0557A}.Release|x64.ActiveCfg = Release|x64 {DA425894-6E13-404F-8DCB-78584EC0557A}.Release|x64.Build.0 = Release|x64 + {DA425894-6E13-404F-8DCB-78584EC0557A}.Release|x86.ActiveCfg = Release|x64 {060D75DA-2D1C-48E6-A4A1-6F0718B64661}.Debug|x64.ActiveCfg = Debug|x64 {060D75DA-2D1C-48E6-A4A1-6F0718B64661}.Debug|x64.Build.0 = Debug|x64 + {060D75DA-2D1C-48E6-A4A1-6F0718B64661}.Debug|x86.ActiveCfg = Debug|x64 {060D75DA-2D1C-48E6-A4A1-6F0718B64661}.Release|x64.ActiveCfg = Release|x64 {060D75DA-2D1C-48E6-A4A1-6F0718B64661}.Release|x64.Build.0 = Release|x64 + {060D75DA-2D1C-48E6-A4A1-6F0718B64661}.Release|x86.ActiveCfg = Release|x64 {748417CA-F17E-487F-9411-CAFB6D3F4877}.Debug|x64.ActiveCfg = Debug|x64 {748417CA-F17E-487F-9411-CAFB6D3F4877}.Debug|x64.Build.0 = Debug|x64 + {748417CA-F17E-487F-9411-CAFB6D3F4877}.Debug|x86.ActiveCfg = Debug|x64 {748417CA-F17E-487F-9411-CAFB6D3F4877}.Release|x64.ActiveCfg = Release|x64 {748417CA-F17E-487F-9411-CAFB6D3F4877}.Release|x64.Build.0 = Release|x64 + {748417CA-F17E-487F-9411-CAFB6D3F4877}.Release|x86.ActiveCfg = Release|x64 {217DF501-135C-4E38-BFC8-99D4821032EA}.Debug|x64.ActiveCfg = Debug|x64 {217DF501-135C-4E38-BFC8-99D4821032EA}.Debug|x64.Build.0 = Debug|x64 + {217DF501-135C-4E38-BFC8-99D4821032EA}.Debug|x86.ActiveCfg = Debug|x64 {217DF501-135C-4E38-BFC8-99D4821032EA}.Release|x64.ActiveCfg = Release|x64 {217DF501-135C-4E38-BFC8-99D4821032EA}.Release|x64.Build.0 = Release|x64 + {217DF501-135C-4E38-BFC8-99D4821032EA}.Release|x86.ActiveCfg = Release|x64 {47310AB4-9034-4BD1-8D8B-E88AD21A171B}.Debug|x64.ActiveCfg = Debug|x64 {47310AB4-9034-4BD1-8D8B-E88AD21A171B}.Debug|x64.Build.0 = Debug|x64 + {47310AB4-9034-4BD1-8D8B-E88AD21A171B}.Debug|x86.ActiveCfg = Debug|x64 {47310AB4-9034-4BD1-8D8B-E88AD21A171B}.Release|x64.ActiveCfg = Release|x64 {47310AB4-9034-4BD1-8D8B-E88AD21A171B}.Release|x64.Build.0 = Release|x64 + {47310AB4-9034-4BD1-8D8B-E88AD21A171B}.Release|x86.ActiveCfg = Release|x64 {A7D5099E-F0FD-4BF3-8522-5A682759F915}.Debug|x64.ActiveCfg = Debug|x64 {A7D5099E-F0FD-4BF3-8522-5A682759F915}.Debug|x64.Build.0 = Debug|x64 + {A7D5099E-F0FD-4BF3-8522-5A682759F915}.Debug|x86.ActiveCfg = Debug|Win32 + {A7D5099E-F0FD-4BF3-8522-5A682759F915}.Debug|x86.Build.0 = Debug|Win32 + {A7D5099E-F0FD-4BF3-8522-5A682759F915}.Debug|x86.Deploy.0 = Debug|Win32 {A7D5099E-F0FD-4BF3-8522-5A682759F915}.Release|x64.ActiveCfg = Release|x64 {A7D5099E-F0FD-4BF3-8522-5A682759F915}.Release|x64.Build.0 = Release|x64 + {A7D5099E-F0FD-4BF3-8522-5A682759F915}.Release|x86.ActiveCfg = Release|Win32 + {A7D5099E-F0FD-4BF3-8522-5A682759F915}.Release|x86.Build.0 = Release|Win32 + {A7D5099E-F0FD-4BF3-8522-5A682759F915}.Release|x86.Deploy.0 = Release|Win32 {B1BCC8C6-46B5-4BFA-8F22-20F32D99EC6A}.Debug|x64.ActiveCfg = Debug|x64 {B1BCC8C6-46B5-4BFA-8F22-20F32D99EC6A}.Debug|x64.Build.0 = Debug|x64 + {B1BCC8C6-46B5-4BFA-8F22-20F32D99EC6A}.Debug|x86.ActiveCfg = Debug|x64 {B1BCC8C6-46B5-4BFA-8F22-20F32D99EC6A}.Release|x64.ActiveCfg = Release|x64 {B1BCC8C6-46B5-4BFA-8F22-20F32D99EC6A}.Release|x64.Build.0 = Release|x64 + {B1BCC8C6-46B5-4BFA-8F22-20F32D99EC6A}.Release|x86.ActiveCfg = Release|x64 {F055103B-F80B-4D0C-BF48-057C55620033}.Debug|x64.ActiveCfg = Debug|x64 {F055103B-F80B-4D0C-BF48-057C55620033}.Debug|x64.Build.0 = Debug|x64 + {F055103B-F80B-4D0C-BF48-057C55620033}.Debug|x86.ActiveCfg = Debug|x64 {F055103B-F80B-4D0C-BF48-057C55620033}.Release|x64.ActiveCfg = Release|x64 {F055103B-F80B-4D0C-BF48-057C55620033}.Release|x64.Build.0 = Release|x64 + {F055103B-F80B-4D0C-BF48-057C55620033}.Release|x86.ActiveCfg = Release|x64 {787B8AA6-CA93-4C84-96FE-DF31110AD1C4}.Debug|x64.ActiveCfg = Debug|x64 {787B8AA6-CA93-4C84-96FE-DF31110AD1C4}.Debug|x64.Build.0 = Debug|x64 + {787B8AA6-CA93-4C84-96FE-DF31110AD1C4}.Debug|x86.ActiveCfg = Debug|x64 {787B8AA6-CA93-4C84-96FE-DF31110AD1C4}.Release|x64.ActiveCfg = Release|x64 {787B8AA6-CA93-4C84-96FE-DF31110AD1C4}.Release|x64.Build.0 = Release|x64 + {787B8AA6-CA93-4C84-96FE-DF31110AD1C4}.Release|x86.ActiveCfg = Release|x64 {08C8C05F-0362-41BC-818C-724572DF8B06}.Debug|x64.ActiveCfg = Debug|x64 {08C8C05F-0362-41BC-818C-724572DF8B06}.Debug|x64.Build.0 = Debug|x64 + {08C8C05F-0362-41BC-818C-724572DF8B06}.Debug|x86.ActiveCfg = Debug|x64 {08C8C05F-0362-41BC-818C-724572DF8B06}.Release|x64.ActiveCfg = Release|x64 {08C8C05F-0362-41BC-818C-724572DF8B06}.Release|x64.Build.0 = Release|x64 + {08C8C05F-0362-41BC-818C-724572DF8B06}.Release|x86.ActiveCfg = Release|x64 {5D00D290-4016-4CFE-9E41-1E7C724509BA}.Debug|x64.ActiveCfg = Debug|x64 {5D00D290-4016-4CFE-9E41-1E7C724509BA}.Debug|x64.Build.0 = Debug|x64 + {5D00D290-4016-4CFE-9E41-1E7C724509BA}.Debug|x86.ActiveCfg = Debug|x64 {5D00D290-4016-4CFE-9E41-1E7C724509BA}.Release|x64.ActiveCfg = Release|x64 {5D00D290-4016-4CFE-9E41-1E7C724509BA}.Release|x64.Build.0 = Release|x64 + {5D00D290-4016-4CFE-9E41-1E7C724509BA}.Release|x86.ActiveCfg = Release|x64 {4AED67B6-55FD-486F-B917-E543DEE2CB3C}.Debug|x64.ActiveCfg = Debug|x64 {4AED67B6-55FD-486F-B917-E543DEE2CB3C}.Debug|x64.Build.0 = Debug|x64 + {4AED67B6-55FD-486F-B917-E543DEE2CB3C}.Debug|x86.ActiveCfg = Debug|x64 {4AED67B6-55FD-486F-B917-E543DEE2CB3C}.Release|x64.ActiveCfg = Release|x64 {4AED67B6-55FD-486F-B917-E543DEE2CB3C}.Release|x64.Build.0 = Release|x64 + {4AED67B6-55FD-486F-B917-E543DEE2CB3C}.Release|x86.ActiveCfg = Release|x64 {42851751-CBC8-45A6-97F5-7A0753F7B4D1}.Debug|x64.ActiveCfg = Debug|x64 {42851751-CBC8-45A6-97F5-7A0753F7B4D1}.Debug|x64.Build.0 = Debug|x64 + {42851751-CBC8-45A6-97F5-7A0753F7B4D1}.Debug|x86.ActiveCfg = Debug|x64 {42851751-CBC8-45A6-97F5-7A0753F7B4D1}.Release|x64.ActiveCfg = Release|x64 {42851751-CBC8-45A6-97F5-7A0753F7B4D1}.Release|x64.Build.0 = Release|x64 + {42851751-CBC8-45A6-97F5-7A0753F7B4D1}.Release|x86.ActiveCfg = Release|x64 {1EF1EEF0-10F0-4F2E-8550-39B6D8044D3E}.Debug|x64.ActiveCfg = Debug|x64 {1EF1EEF0-10F0-4F2E-8550-39B6D8044D3E}.Debug|x64.Build.0 = Debug|x64 + {1EF1EEF0-10F0-4F2E-8550-39B6D8044D3E}.Debug|x86.ActiveCfg = Debug|x64 {1EF1EEF0-10F0-4F2E-8550-39B6D8044D3E}.Release|x64.ActiveCfg = Release|x64 {1EF1EEF0-10F0-4F2E-8550-39B6D8044D3E}.Release|x64.Build.0 = Release|x64 + {1EF1EEF0-10F0-4F2E-8550-39B6D8044D3E}.Release|x86.ActiveCfg = Release|x64 {8FFE09DA-FA4F-4EE1-B3A2-AD5497FBD1AD}.Debug|x64.ActiveCfg = Debug|x64 {8FFE09DA-FA4F-4EE1-B3A2-AD5497FBD1AD}.Debug|x64.Build.0 = Debug|x64 + {8FFE09DA-FA4F-4EE1-B3A2-AD5497FBD1AD}.Debug|x86.ActiveCfg = Debug|x64 {8FFE09DA-FA4F-4EE1-B3A2-AD5497FBD1AD}.Release|x64.ActiveCfg = Release|x64 {8FFE09DA-FA4F-4EE1-B3A2-AD5497FBD1AD}.Release|x64.Build.0 = Release|x64 + {8FFE09DA-FA4F-4EE1-B3A2-AD5497FBD1AD}.Release|x86.ActiveCfg = Release|x64 {655C9AF2-18D3-4DA6-80E4-85504A7722BA}.Debug|x64.ActiveCfg = Debug|x64 {655C9AF2-18D3-4DA6-80E4-85504A7722BA}.Debug|x64.Build.0 = Debug|x64 + {655C9AF2-18D3-4DA6-80E4-85504A7722BA}.Debug|x86.ActiveCfg = Debug|x64 {655C9AF2-18D3-4DA6-80E4-85504A7722BA}.Release|x64.ActiveCfg = Release|x64 {655C9AF2-18D3-4DA6-80E4-85504A7722BA}.Release|x64.Build.0 = Release|x64 + {655C9AF2-18D3-4DA6-80E4-85504A7722BA}.Release|x86.ActiveCfg = Release|x64 {BA58206B-1493-4C75-BFEA-A85768A1E156}.Debug|x64.ActiveCfg = Debug|x64 {BA58206B-1493-4C75-BFEA-A85768A1E156}.Debug|x64.Build.0 = Debug|x64 + {BA58206B-1493-4C75-BFEA-A85768A1E156}.Debug|x86.ActiveCfg = Debug|x64 {BA58206B-1493-4C75-BFEA-A85768A1E156}.Release|x64.ActiveCfg = Release|x64 {BA58206B-1493-4C75-BFEA-A85768A1E156}.Release|x64.Build.0 = Release|x64 + {BA58206B-1493-4C75-BFEA-A85768A1E156}.Release|x86.ActiveCfg = Release|x64 {03276A39-D4E9-417C-8FFD-200B0EE5E871}.Debug|x64.ActiveCfg = Debug|x64 {03276A39-D4E9-417C-8FFD-200B0EE5E871}.Debug|x64.Build.0 = Debug|x64 + {03276A39-D4E9-417C-8FFD-200B0EE5E871}.Debug|x86.ActiveCfg = Debug|x64 {03276A39-D4E9-417C-8FFD-200B0EE5E871}.Release|x64.ActiveCfg = Release|x64 {03276A39-D4E9-417C-8FFD-200B0EE5E871}.Release|x64.Build.0 = Release|x64 + {03276A39-D4E9-417C-8FFD-200B0EE5E871}.Release|x86.ActiveCfg = Release|x64 {B81FB7B6-D30E-428F-908A-41422EFC1172}.Debug|x64.ActiveCfg = Debug|x64 {B81FB7B6-D30E-428F-908A-41422EFC1172}.Debug|x64.Build.0 = Debug|x64 + {B81FB7B6-D30E-428F-908A-41422EFC1172}.Debug|x86.ActiveCfg = Debug|x64 {B81FB7B6-D30E-428F-908A-41422EFC1172}.Release|x64.ActiveCfg = Release|x64 {B81FB7B6-D30E-428F-908A-41422EFC1172}.Release|x64.Build.0 = Release|x64 + {B81FB7B6-D30E-428F-908A-41422EFC1172}.Release|x86.ActiveCfg = Release|x64 {0F85E674-34AE-443D-954C-8321EB8B93B1}.Debug|x64.ActiveCfg = Debug|x64 {0F85E674-34AE-443D-954C-8321EB8B93B1}.Debug|x64.Build.0 = Debug|x64 + {0F85E674-34AE-443D-954C-8321EB8B93B1}.Debug|x86.ActiveCfg = Debug|x64 {0F85E674-34AE-443D-954C-8321EB8B93B1}.Release|x64.ActiveCfg = Release|x64 {0F85E674-34AE-443D-954C-8321EB8B93B1}.Release|x64.Build.0 = Release|x64 + {0F85E674-34AE-443D-954C-8321EB8B93B1}.Release|x86.ActiveCfg = Release|x64 {632BBE62-5421-49EA-835A-7FFA4F499BD6}.Debug|x64.ActiveCfg = Debug|x64 {632BBE62-5421-49EA-835A-7FFA4F499BD6}.Debug|x64.Build.0 = Debug|x64 + {632BBE62-5421-49EA-835A-7FFA4F499BD6}.Debug|x86.ActiveCfg = Debug|x64 {632BBE62-5421-49EA-835A-7FFA4F499BD6}.Release|x64.ActiveCfg = Release|x64 {632BBE62-5421-49EA-835A-7FFA4F499BD6}.Release|x64.Build.0 = Release|x64 + {632BBE62-5421-49EA-835A-7FFA4F499BD6}.Release|x86.ActiveCfg = Release|x64 {4FA206A5-F69F-4193-BF8F-F6EEB496734C}.Debug|x64.ActiveCfg = Debug|x64 {4FA206A5-F69F-4193-BF8F-F6EEB496734C}.Debug|x64.Build.0 = Debug|x64 + {4FA206A5-F69F-4193-BF8F-F6EEB496734C}.Debug|x86.ActiveCfg = Debug|x64 {4FA206A5-F69F-4193-BF8F-F6EEB496734C}.Release|x64.ActiveCfg = Release|x64 {4FA206A5-F69F-4193-BF8F-F6EEB496734C}.Release|x64.Build.0 = Release|x64 + {4FA206A5-F69F-4193-BF8F-F6EEB496734C}.Release|x86.ActiveCfg = Release|x64 {090CD7B7-3B0C-4D1D-BC98-83EB5D799BC1}.Debug|x64.ActiveCfg = Debug|x64 {090CD7B7-3B0C-4D1D-BC98-83EB5D799BC1}.Debug|x64.Build.0 = Debug|x64 + {090CD7B7-3B0C-4D1D-BC98-83EB5D799BC1}.Debug|x86.ActiveCfg = Debug|x64 {090CD7B7-3B0C-4D1D-BC98-83EB5D799BC1}.Release|x64.ActiveCfg = Release|x64 {090CD7B7-3B0C-4D1D-BC98-83EB5D799BC1}.Release|x64.Build.0 = Release|x64 + {090CD7B7-3B0C-4D1D-BC98-83EB5D799BC1}.Release|x86.ActiveCfg = Release|x64 {7E1E3F13-2BD6-3F75-A6A7-873A2B55C60F}.Debug|x64.ActiveCfg = Debug|x64 {7E1E3F13-2BD6-3F75-A6A7-873A2B55C60F}.Debug|x64.Build.0 = Debug|x64 + {7E1E3F13-2BD6-3F75-A6A7-873A2B55C60F}.Debug|x86.ActiveCfg = Debug|x64 {7E1E3F13-2BD6-3F75-A6A7-873A2B55C60F}.Release|x64.ActiveCfg = Release|x64 {7E1E3F13-2BD6-3F75-A6A7-873A2B55C60F}.Release|x64.Build.0 = Release|x64 + {7E1E3F13-2BD6-3F75-A6A7-873A2B55C60F}.Release|x86.ActiveCfg = Release|x64 {FD8EB419-FF9C-4D88-BB6F-BF6CED37747B}.Debug|x64.ActiveCfg = Debug|x64 {FD8EB419-FF9C-4D88-BB6F-BF6CED37747B}.Debug|x64.Build.0 = Debug|x64 + {FD8EB419-FF9C-4D88-BB6F-BF6CED37747B}.Debug|x86.ActiveCfg = Debug|x64 {FD8EB419-FF9C-4D88-BB6F-BF6CED37747B}.Release|x64.ActiveCfg = Release|x64 {FD8EB419-FF9C-4D88-BB6F-BF6CED37747B}.Release|x64.Build.0 = Release|x64 + {FD8EB419-FF9C-4D88-BB6F-BF6CED37747B}.Release|x86.ActiveCfg = Release|x64 {DA5A6FE9-0040-40CC-83CC-764AE5306590}.Debug|x64.ActiveCfg = Debug|x64 {DA5A6FE9-0040-40CC-83CC-764AE5306590}.Debug|x64.Build.0 = Debug|x64 + {DA5A6FE9-0040-40CC-83CC-764AE5306590}.Debug|x86.ActiveCfg = Debug|x64 {DA5A6FE9-0040-40CC-83CC-764AE5306590}.Release|x64.ActiveCfg = Release|x64 {DA5A6FE9-0040-40CC-83CC-764AE5306590}.Release|x64.Build.0 = Release|x64 + {DA5A6FE9-0040-40CC-83CC-764AE5306590}.Release|x86.ActiveCfg = Release|x64 {0351ADA4-0C32-4652-9BA0-41F7B602372B}.Debug|x64.ActiveCfg = Debug|x64 {0351ADA4-0C32-4652-9BA0-41F7B602372B}.Debug|x64.Build.0 = Debug|x64 + {0351ADA4-0C32-4652-9BA0-41F7B602372B}.Debug|x86.ActiveCfg = Debug|x64 {0351ADA4-0C32-4652-9BA0-41F7B602372B}.Release|x64.ActiveCfg = Release|x64 {0351ADA4-0C32-4652-9BA0-41F7B602372B}.Release|x64.Build.0 = Release|x64 + {0351ADA4-0C32-4652-9BA0-41F7B602372B}.Release|x86.ActiveCfg = Release|x64 {D9B8FC84-322A-4F9F-BBB9-20915C47DDFD}.Debug|x64.ActiveCfg = Debug|x64 {D9B8FC84-322A-4F9F-BBB9-20915C47DDFD}.Debug|x64.Build.0 = Debug|x64 + {D9B8FC84-322A-4F9F-BBB9-20915C47DDFD}.Debug|x86.ActiveCfg = Debug|x64 {D9B8FC84-322A-4F9F-BBB9-20915C47DDFD}.Release|x64.ActiveCfg = Release|x64 {D9B8FC84-322A-4F9F-BBB9-20915C47DDFD}.Release|x64.Build.0 = Release|x64 + {D9B8FC84-322A-4F9F-BBB9-20915C47DDFD}.Release|x86.ActiveCfg = Release|x64 {6955446D-23F7-4023-9BB3-8657F904AF99}.Debug|x64.ActiveCfg = Debug|x64 {6955446D-23F7-4023-9BB3-8657F904AF99}.Debug|x64.Build.0 = Debug|x64 + {6955446D-23F7-4023-9BB3-8657F904AF99}.Debug|x86.ActiveCfg = Debug|x64 {6955446D-23F7-4023-9BB3-8657F904AF99}.Release|x64.ActiveCfg = Release|x64 {6955446D-23F7-4023-9BB3-8657F904AF99}.Release|x64.Build.0 = Release|x64 + {6955446D-23F7-4023-9BB3-8657F904AF99}.Release|x86.ActiveCfg = Release|x64 {58736667-1027-4AD7-BFDF-7A3A6474103A}.Debug|x64.ActiveCfg = Debug|x64 {58736667-1027-4AD7-BFDF-7A3A6474103A}.Debug|x64.Build.0 = Debug|x64 + {58736667-1027-4AD7-BFDF-7A3A6474103A}.Debug|x86.ActiveCfg = Debug|x64 {58736667-1027-4AD7-BFDF-7A3A6474103A}.Release|x64.ActiveCfg = Release|x64 {58736667-1027-4AD7-BFDF-7A3A6474103A}.Release|x64.Build.0 = Release|x64 + {58736667-1027-4AD7-BFDF-7A3A6474103A}.Release|x86.ActiveCfg = Release|x64 {1D5BE09D-78C0-4FD7-AF00-AE7C1AF7C525}.Debug|x64.ActiveCfg = Debug|x64 {1D5BE09D-78C0-4FD7-AF00-AE7C1AF7C525}.Debug|x64.Build.0 = Debug|x64 + {1D5BE09D-78C0-4FD7-AF00-AE7C1AF7C525}.Debug|x86.ActiveCfg = Debug|x64 {1D5BE09D-78C0-4FD7-AF00-AE7C1AF7C525}.Release|x64.ActiveCfg = Release|x64 {1D5BE09D-78C0-4FD7-AF00-AE7C1AF7C525}.Release|x64.Build.0 = Release|x64 + {1D5BE09D-78C0-4FD7-AF00-AE7C1AF7C525}.Release|x86.ActiveCfg = Release|x64 {031AC72E-FA28-4AB7-B690-6F7B9C28AA73}.Debug|x64.ActiveCfg = Debug|x64 {031AC72E-FA28-4AB7-B690-6F7B9C28AA73}.Debug|x64.Build.0 = Debug|x64 + {031AC72E-FA28-4AB7-B690-6F7B9C28AA73}.Debug|x86.ActiveCfg = Debug|x64 {031AC72E-FA28-4AB7-B690-6F7B9C28AA73}.Release|x64.ActiveCfg = Release|x64 {031AC72E-FA28-4AB7-B690-6F7B9C28AA73}.Release|x64.Build.0 = Release|x64 + {031AC72E-FA28-4AB7-B690-6F7B9C28AA73}.Release|x86.ActiveCfg = Release|x64 {0B593A6C-4143-4337-860E-DB5710FB87DB}.Debug|x64.ActiveCfg = Debug|x64 {0B593A6C-4143-4337-860E-DB5710FB87DB}.Debug|x64.Build.0 = Debug|x64 + {0B593A6C-4143-4337-860E-DB5710FB87DB}.Debug|x86.ActiveCfg = Debug|x64 {0B593A6C-4143-4337-860E-DB5710FB87DB}.Release|x64.ActiveCfg = Release|x64 {0B593A6C-4143-4337-860E-DB5710FB87DB}.Release|x64.Build.0 = Release|x64 + {0B593A6C-4143-4337-860E-DB5710FB87DB}.Release|x86.ActiveCfg = Release|x64 {CC6E41AC-8174-4E8A-8D22-85DD7F4851DF}.Debug|x64.ActiveCfg = Debug|x64 {CC6E41AC-8174-4E8A-8D22-85DD7F4851DF}.Debug|x64.Build.0 = Debug|x64 + {CC6E41AC-8174-4E8A-8D22-85DD7F4851DF}.Debug|x86.ActiveCfg = Debug|x64 {CC6E41AC-8174-4E8A-8D22-85DD7F4851DF}.Release|x64.ActiveCfg = Release|x64 {CC6E41AC-8174-4E8A-8D22-85DD7F4851DF}.Release|x64.Build.0 = Release|x64 + {CC6E41AC-8174-4E8A-8D22-85DD7F4851DF}.Release|x86.ActiveCfg = Release|x64 {7319089E-46D6-4400-BC65-E39BDF1416EE}.Debug|x64.ActiveCfg = Debug|x64 {7319089E-46D6-4400-BC65-E39BDF1416EE}.Debug|x64.Build.0 = Debug|x64 + {7319089E-46D6-4400-BC65-E39BDF1416EE}.Debug|x86.ActiveCfg = Debug|x64 {7319089E-46D6-4400-BC65-E39BDF1416EE}.Release|x64.ActiveCfg = Release|x64 {7319089E-46D6-4400-BC65-E39BDF1416EE}.Release|x64.Build.0 = Release|x64 + {7319089E-46D6-4400-BC65-E39BDF1416EE}.Release|x86.ActiveCfg = Release|x64 {CABA8DFB-823B-4BF2-93AC-3F31984150D9}.Debug|x64.ActiveCfg = Debug|x64 {CABA8DFB-823B-4BF2-93AC-3F31984150D9}.Debug|x64.Build.0 = Debug|x64 + {CABA8DFB-823B-4BF2-93AC-3F31984150D9}.Debug|x86.ActiveCfg = Debug|x64 {CABA8DFB-823B-4BF2-93AC-3F31984150D9}.Release|x64.ActiveCfg = Release|x64 {CABA8DFB-823B-4BF2-93AC-3F31984150D9}.Release|x64.Build.0 = Release|x64 + {CABA8DFB-823B-4BF2-93AC-3F31984150D9}.Release|x86.ActiveCfg = Release|x64 {98537082-0FDB-40DE-ABD8-0DC5A4269BAB}.Debug|x64.ActiveCfg = Debug|x64 {98537082-0FDB-40DE-ABD8-0DC5A4269BAB}.Debug|x64.Build.0 = Debug|x64 + {98537082-0FDB-40DE-ABD8-0DC5A4269BAB}.Debug|x86.ActiveCfg = Debug|x64 {98537082-0FDB-40DE-ABD8-0DC5A4269BAB}.Release|x64.ActiveCfg = Release|x64 {98537082-0FDB-40DE-ABD8-0DC5A4269BAB}.Release|x64.Build.0 = Release|x64 + {98537082-0FDB-40DE-ABD8-0DC5A4269BAB}.Release|x86.ActiveCfg = Release|x64 {C3A17DCA-217B-462C-BB0C-BE086AF80081}.Debug|x64.ActiveCfg = Debug|x64 {C3A17DCA-217B-462C-BB0C-BE086AF80081}.Debug|x64.Build.0 = Debug|x64 + {C3A17DCA-217B-462C-BB0C-BE086AF80081}.Debug|x86.ActiveCfg = Debug|x64 {C3A17DCA-217B-462C-BB0C-BE086AF80081}.Release|x64.ActiveCfg = Release|x64 {C3A17DCA-217B-462C-BB0C-BE086AF80081}.Release|x64.Build.0 = Release|x64 + {C3A17DCA-217B-462C-BB0C-BE086AF80081}.Release|x86.ActiveCfg = Release|x64 {4BABF3FE-3451-42FD-873F-3C332E18DCEF}.Debug|x64.ActiveCfg = Debug|x64 {4BABF3FE-3451-42FD-873F-3C332E18DCEF}.Debug|x64.Build.0 = Debug|x64 + {4BABF3FE-3451-42FD-873F-3C332E18DCEF}.Debug|x86.ActiveCfg = Debug|x64 {4BABF3FE-3451-42FD-873F-3C332E18DCEF}.Release|x64.ActiveCfg = Release|x64 {4BABF3FE-3451-42FD-873F-3C332E18DCEF}.Release|x64.Build.0 = Release|x64 + {4BABF3FE-3451-42FD-873F-3C332E18DCEF}.Release|x86.ActiveCfg = Release|x64 {0648DF05-5DDA-4BE1-B5F2-584926EBDB65}.Debug|x64.ActiveCfg = Debug|x64 {0648DF05-5DDA-4BE1-B5F2-584926EBDB65}.Debug|x64.Build.0 = Debug|x64 + {0648DF05-5DDA-4BE1-B5F2-584926EBDB65}.Debug|x86.ActiveCfg = Debug|x64 {0648DF05-5DDA-4BE1-B5F2-584926EBDB65}.Release|x64.ActiveCfg = Release|x64 {0648DF05-5DDA-4BE1-B5F2-584926EBDB65}.Release|x64.Build.0 = Release|x64 + {0648DF05-5DDA-4BE1-B5F2-584926EBDB65}.Release|x86.ActiveCfg = Release|x64 {6ED2F4FC-E122-4CEE-90F1-97E4CCC8BC7A}.Debug|x64.ActiveCfg = Debug|x64 {6ED2F4FC-E122-4CEE-90F1-97E4CCC8BC7A}.Debug|x64.Build.0 = Debug|x64 + {6ED2F4FC-E122-4CEE-90F1-97E4CCC8BC7A}.Debug|x86.ActiveCfg = Debug|x64 {6ED2F4FC-E122-4CEE-90F1-97E4CCC8BC7A}.Release|x64.ActiveCfg = Release|x64 {6ED2F4FC-E122-4CEE-90F1-97E4CCC8BC7A}.Release|x64.Build.0 = Release|x64 + {6ED2F4FC-E122-4CEE-90F1-97E4CCC8BC7A}.Release|x86.ActiveCfg = Release|x64 {BA661F5B-1D5A-4FFC-9BF1-FC39DF280BDD}.Debug|x64.ActiveCfg = Debug|x64 {BA661F5B-1D5A-4FFC-9BF1-FC39DF280BDD}.Debug|x64.Build.0 = Debug|x64 + {BA661F5B-1D5A-4FFC-9BF1-FC39DF280BDD}.Debug|x86.ActiveCfg = Debug|x64 {BA661F5B-1D5A-4FFC-9BF1-FC39DF280BDD}.Release|x64.ActiveCfg = Release|x64 {BA661F5B-1D5A-4FFC-9BF1-FC39DF280BDD}.Release|x64.Build.0 = Release|x64 + {BA661F5B-1D5A-4FFC-9BF1-FC39DF280BDD}.Release|x86.ActiveCfg = Release|x64 {E496B7FC-1E99-4BAB-849B-0E8367040B02}.Debug|x64.ActiveCfg = Debug|x64 {E496B7FC-1E99-4BAB-849B-0E8367040B02}.Debug|x64.Build.0 = Debug|x64 + {E496B7FC-1E99-4BAB-849B-0E8367040B02}.Debug|x86.ActiveCfg = Debug|x64 {E496B7FC-1E99-4BAB-849B-0E8367040B02}.Release|x64.ActiveCfg = Release|x64 {E496B7FC-1E99-4BAB-849B-0E8367040B02}.Release|x64.Build.0 = Release|x64 + {E496B7FC-1E99-4BAB-849B-0E8367040B02}.Release|x86.ActiveCfg = Release|x64 {7F4B3A60-BC27-45A7-8000-68B0B6EA7466}.Debug|x64.ActiveCfg = Debug|x64 {7F4B3A60-BC27-45A7-8000-68B0B6EA7466}.Debug|x64.Build.0 = Debug|x64 + {7F4B3A60-BC27-45A7-8000-68B0B6EA7466}.Debug|x86.ActiveCfg = Debug|x64 {7F4B3A60-BC27-45A7-8000-68B0B6EA7466}.Release|x64.ActiveCfg = Release|x64 {7F4B3A60-BC27-45A7-8000-68B0B6EA7466}.Release|x64.Build.0 = Release|x64 + {7F4B3A60-BC27-45A7-8000-68B0B6EA7466}.Release|x86.ActiveCfg = Release|x64 {8DF78B53-200E-451F-9328-01EB907193AE}.Debug|x64.ActiveCfg = Debug|x64 {8DF78B53-200E-451F-9328-01EB907193AE}.Debug|x64.Build.0 = Debug|x64 + {8DF78B53-200E-451F-9328-01EB907193AE}.Debug|x86.ActiveCfg = Debug|x64 {8DF78B53-200E-451F-9328-01EB907193AE}.Release|x64.ActiveCfg = Release|x64 {8DF78B53-200E-451F-9328-01EB907193AE}.Release|x64.Build.0 = Release|x64 + {8DF78B53-200E-451F-9328-01EB907193AE}.Release|x86.ActiveCfg = Release|x64 {23D2070D-E4AD-4ADD-85A7-083D9C76AD49}.Debug|x64.ActiveCfg = Debug|x64 {23D2070D-E4AD-4ADD-85A7-083D9C76AD49}.Debug|x64.Build.0 = Debug|x64 + {23D2070D-E4AD-4ADD-85A7-083D9C76AD49}.Debug|x86.ActiveCfg = Debug|x64 {23D2070D-E4AD-4ADD-85A7-083D9C76AD49}.Release|x64.ActiveCfg = Release|x64 {23D2070D-E4AD-4ADD-85A7-083D9C76AD49}.Release|x64.Build.0 = Release|x64 + {23D2070D-E4AD-4ADD-85A7-083D9C76AD49}.Release|x86.ActiveCfg = Release|x64 {62173D9A-6724-4C00-A1C8-FB646480A9EC}.Debug|x64.ActiveCfg = Debug|x64 {62173D9A-6724-4C00-A1C8-FB646480A9EC}.Debug|x64.Build.0 = Debug|x64 + {62173D9A-6724-4C00-A1C8-FB646480A9EC}.Debug|x86.ActiveCfg = Debug|x64 {62173D9A-6724-4C00-A1C8-FB646480A9EC}.Release|x64.ActiveCfg = Release|x64 {62173D9A-6724-4C00-A1C8-FB646480A9EC}.Release|x64.Build.0 = Release|x64 - {BB23A474-5058-4F75-8FA3-5FE3DE53CDF4}.Debug|x64.ActiveCfg = Debug|x64 - {BB23A474-5058-4F75-8FA3-5FE3DE53CDF4}.Debug|x64.Build.0 = Debug|x64 - {BB23A474-5058-4F75-8FA3-5FE3DE53CDF4}.Release|x64.ActiveCfg = Release|x64 - {BB23A474-5058-4F75-8FA3-5FE3DE53CDF4}.Release|x64.Build.0 = Release|x64 + {62173D9A-6724-4C00-A1C8-FB646480A9EC}.Release|x86.ActiveCfg = Release|x64 {5E7360A8-D048-4ED3-8F09-0BFD64C5529A}.Debug|x64.ActiveCfg = Debug|x64 {5E7360A8-D048-4ED3-8F09-0BFD64C5529A}.Debug|x64.Build.0 = Debug|x64 + {5E7360A8-D048-4ED3-8F09-0BFD64C5529A}.Debug|x86.ActiveCfg = Debug|x64 {5E7360A8-D048-4ED3-8F09-0BFD64C5529A}.Release|x64.ActiveCfg = Release|x64 {5E7360A8-D048-4ED3-8F09-0BFD64C5529A}.Release|x64.Build.0 = Release|x64 - {3E424AD2-19E5-4AE6-B833-F53963EB5FC1}.Debug|x64.ActiveCfg = Debug|x64 - {3E424AD2-19E5-4AE6-B833-F53963EB5FC1}.Debug|x64.Build.0 = Debug|x64 - {3E424AD2-19E5-4AE6-B833-F53963EB5FC1}.Release|x64.ActiveCfg = Release|x64 - {3E424AD2-19E5-4AE6-B833-F53963EB5FC1}.Release|x64.Build.0 = Release|x64 + {5E7360A8-D048-4ED3-8F09-0BFD64C5529A}.Release|x86.ActiveCfg = Release|x64 {D940E07F-532C-4FF3-883F-790DA014F19A}.Debug|x64.ActiveCfg = Debug|x64 {D940E07F-532C-4FF3-883F-790DA014F19A}.Debug|x64.Build.0 = Debug|x64 + {D940E07F-532C-4FF3-883F-790DA014F19A}.Debug|x86.ActiveCfg = Debug|x64 {D940E07F-532C-4FF3-883F-790DA014F19A}.Release|x64.ActiveCfg = Release|x64 {D940E07F-532C-4FF3-883F-790DA014F19A}.Release|x64.Build.0 = Release|x64 + {D940E07F-532C-4FF3-883F-790DA014F19A}.Release|x86.ActiveCfg = Release|x64 + {BB23A474-5058-4F75-8FA3-5FE3DE53CDF4}.Debug|x64.ActiveCfg = Debug|x64 + {BB23A474-5058-4F75-8FA3-5FE3DE53CDF4}.Debug|x64.Build.0 = Debug|x64 + {BB23A474-5058-4F75-8FA3-5FE3DE53CDF4}.Debug|x86.ActiveCfg = Debug|x64 + {BB23A474-5058-4F75-8FA3-5FE3DE53CDF4}.Release|x64.ActiveCfg = Release|x64 + {BB23A474-5058-4F75-8FA3-5FE3DE53CDF4}.Release|x64.Build.0 = Release|x64 + {BB23A474-5058-4F75-8FA3-5FE3DE53CDF4}.Release|x86.ActiveCfg = Release|x64 + {3E424AD2-19E5-4AE6-B833-F53963EB5FC1}.Debug|x64.ActiveCfg = Debug|x64 + {3E424AD2-19E5-4AE6-B833-F53963EB5FC1}.Debug|x64.Build.0 = Debug|x64 + {3E424AD2-19E5-4AE6-B833-F53963EB5FC1}.Debug|x86.ActiveCfg = Debug|x64 + {3E424AD2-19E5-4AE6-B833-F53963EB5FC1}.Release|x64.ActiveCfg = Release|x64 + {3E424AD2-19E5-4AE6-B833-F53963EB5FC1}.Release|x64.Build.0 = Release|x64 + {3E424AD2-19E5-4AE6-B833-F53963EB5FC1}.Release|x86.ActiveCfg = Release|x64 {2D604C07-51FC-46BB-9EB7-75AECC7F5E81}.Debug|x64.ActiveCfg = Debug|x64 {2D604C07-51FC-46BB-9EB7-75AECC7F5E81}.Debug|x64.Build.0 = Debug|x64 + {2D604C07-51FC-46BB-9EB7-75AECC7F5E81}.Debug|x86.ActiveCfg = Debug|x64 {2D604C07-51FC-46BB-9EB7-75AECC7F5E81}.Release|x64.ActiveCfg = Release|x64 {2D604C07-51FC-46BB-9EB7-75AECC7F5E81}.Release|x64.Build.0 = Release|x64 + {2D604C07-51FC-46BB-9EB7-75AECC7F5E81}.Release|x86.ActiveCfg = Release|x64 {2EDB3EB4-FA92-4BFF-B2D8-566584837231}.Debug|x64.ActiveCfg = Debug|x64 {2EDB3EB4-FA92-4BFF-B2D8-566584837231}.Debug|x64.Build.0 = Debug|x64 + {2EDB3EB4-FA92-4BFF-B2D8-566584837231}.Debug|x86.ActiveCfg = Debug|x64 {2EDB3EB4-FA92-4BFF-B2D8-566584837231}.Release|x64.ActiveCfg = Release|x64 {2EDB3EB4-FA92-4BFF-B2D8-566584837231}.Release|x64.Build.0 = Release|x64 + {2EDB3EB4-FA92-4BFF-B2D8-566584837231}.Release|x86.ActiveCfg = Release|x64 {48804216-2A0E-4168-A6D8-9CD068D14227}.Debug|x64.ActiveCfg = Debug|x64 {48804216-2A0E-4168-A6D8-9CD068D14227}.Debug|x64.Build.0 = Debug|x64 + {48804216-2A0E-4168-A6D8-9CD068D14227}.Debug|x86.ActiveCfg = Debug|x64 {48804216-2A0E-4168-A6D8-9CD068D14227}.Release|x64.ActiveCfg = Release|x64 {48804216-2A0E-4168-A6D8-9CD068D14227}.Release|x64.Build.0 = Release|x64 - {390AE700-B55F-4202-91EA-A822EB75B9BD}.Debug|x64.ActiveCfg = Debug|x64 - {390AE700-B55F-4202-91EA-A822EB75B9BD}.Debug|x64.Build.0 = Debug|x64 - {390AE700-B55F-4202-91EA-A822EB75B9BD}.Release|x64.ActiveCfg = Release|x64 - {390AE700-B55F-4202-91EA-A822EB75B9BD}.Release|x64.Build.0 = Release|x64 + {48804216-2A0E-4168-A6D8-9CD068D14227}.Release|x86.ActiveCfg = Release|x64 + {FF1D7936-842A-4BBB-8BEA-E9FE796DE700}.Debug|x64.ActiveCfg = Debug|x64 + {FF1D7936-842A-4BBB-8BEA-E9FE796DE700}.Debug|x64.Build.0 = Debug|x64 + {FF1D7936-842A-4BBB-8BEA-E9FE796DE700}.Debug|x86.ActiveCfg = Debug|x64 + {FF1D7936-842A-4BBB-8BEA-E9FE796DE700}.Release|x64.ActiveCfg = Release|x64 + {FF1D7936-842A-4BBB-8BEA-E9FE796DE700}.Release|x64.Build.0 = Release|x64 + {FF1D7936-842A-4BBB-8BEA-E9FE796DE700}.Release|x86.ActiveCfg = Release|x64 {44CE9AE1-4390-42C5-BACC-0FD6B40AA203}.Debug|x64.ActiveCfg = Debug|x64 {44CE9AE1-4390-42C5-BACC-0FD6B40AA203}.Debug|x64.Build.0 = Debug|x64 + {44CE9AE1-4390-42C5-BACC-0FD6B40AA203}.Debug|x86.ActiveCfg = Debug|x64 {44CE9AE1-4390-42C5-BACC-0FD6B40AA203}.Release|x64.ActiveCfg = Release|x64 {44CE9AE1-4390-42C5-BACC-0FD6B40AA203}.Release|x64.Build.0 = Release|x64 + {44CE9AE1-4390-42C5-BACC-0FD6B40AA203}.Release|x86.ActiveCfg = Release|x64 {5043CECE-E6A7-4867-9CBE-02D27D83747A}.Debug|x64.ActiveCfg = Debug|x64 {5043CECE-E6A7-4867-9CBE-02D27D83747A}.Debug|x64.Build.0 = Debug|x64 + {5043CECE-E6A7-4867-9CBE-02D27D83747A}.Debug|x86.ActiveCfg = Debug|x64 {5043CECE-E6A7-4867-9CBE-02D27D83747A}.Release|x64.ActiveCfg = Release|x64 {5043CECE-E6A7-4867-9CBE-02D27D83747A}.Release|x64.Build.0 = Release|x64 + {5043CECE-E6A7-4867-9CBE-02D27D83747A}.Release|x86.ActiveCfg = Release|x64 + {459E0768-7EBD-4C41-BBA1-6DB3B3815E0A}.Debug|x64.ActiveCfg = Debug|x64 + {459E0768-7EBD-4C41-BBA1-6DB3B3815E0A}.Debug|x64.Build.0 = Debug|x64 + {459E0768-7EBD-4C41-BBA1-6DB3B3815E0A}.Debug|x86.ActiveCfg = Debug|Win32 + {459E0768-7EBD-4C41-BBA1-6DB3B3815E0A}.Debug|x86.Build.0 = Debug|Win32 + {459E0768-7EBD-4C41-BBA1-6DB3B3815E0A}.Release|x64.ActiveCfg = Release|x64 + {459E0768-7EBD-4C41-BBA1-6DB3B3815E0A}.Release|x64.Build.0 = Release|x64 + {459E0768-7EBD-4C41-BBA1-6DB3B3815E0A}.Release|x86.ActiveCfg = Release|Win32 + {459E0768-7EBD-4C41-BBA1-6DB3B3815E0A}.Release|x86.Build.0 = Release|Win32 + {5ABA70DE-3A3F-41F6-A1F5-D1F74F54F9BB}.Debug|x64.ActiveCfg = Debug|x64 + {5ABA70DE-3A3F-41F6-A1F5-D1F74F54F9BB}.Debug|x64.Build.0 = Debug|x64 + {5ABA70DE-3A3F-41F6-A1F5-D1F74F54F9BB}.Debug|x86.ActiveCfg = Debug|x64 + {5ABA70DE-3A3F-41F6-A1F5-D1F74F54F9BB}.Release|x64.ActiveCfg = Release|x64 + {5ABA70DE-3A3F-41F6-A1F5-D1F74F54F9BB}.Release|x64.Build.0 = Release|x64 + {5ABA70DE-3A3F-41F6-A1F5-D1F74F54F9BB}.Release|x86.ActiveCfg = Release|x64 + {AC2857B4-103D-4D6D-9740-926EBF785042}.Debug|x64.ActiveCfg = Debug|x64 + {AC2857B4-103D-4D6D-9740-926EBF785042}.Debug|x64.Build.0 = Debug|x64 + {AC2857B4-103D-4D6D-9740-926EBF785042}.Debug|x86.ActiveCfg = Debug|Win32 + {AC2857B4-103D-4D6D-9740-926EBF785042}.Debug|x86.Build.0 = Debug|Win32 + {AC2857B4-103D-4D6D-9740-926EBF785042}.Release|x64.ActiveCfg = Release|x64 + {AC2857B4-103D-4D6D-9740-926EBF785042}.Release|x64.Build.0 = Release|x64 + {AC2857B4-103D-4D6D-9740-926EBF785042}.Release|x86.ActiveCfg = Release|Win32 + {AC2857B4-103D-4D6D-9740-926EBF785042}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -809,16 +1029,20 @@ Global {23D2070D-E4AD-4ADD-85A7-083D9C76AD49} = {38BDB927-829B-4C65-9CD9-93FB05D66D65} {62173D9A-6724-4C00-A1C8-FB646480A9EC} = {38BDB927-829B-4C65-9CD9-93FB05D66D65} {127F38E0-40AA-4594-B955-5616BF206882} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC} - {BB23A474-5058-4F75-8FA3-5FE3DE53CDF4} = {4AFC9975-2456-4C70-94A4-84073C1CED93} {5E7360A8-D048-4ED3-8F09-0BFD64C5529A} = {127F38E0-40AA-4594-B955-5616BF206882} - {3E424AD2-19E5-4AE6-B833-F53963EB5FC1} = {4AFC9975-2456-4C70-94A4-84073C1CED93} {D940E07F-532C-4FF3-883F-790DA014F19A} = {127F38E0-40AA-4594-B955-5616BF206882} + {BB23A474-5058-4F75-8FA3-5FE3DE53CDF4} = {4AFC9975-2456-4C70-94A4-84073C1CED93} + {3E424AD2-19E5-4AE6-B833-F53963EB5FC1} = {4AFC9975-2456-4C70-94A4-84073C1CED93} {106CBECA-0701-4FC3-838C-9DF816A19AE2} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC} {2D604C07-51FC-46BB-9EB7-75AECC7F5E81} = {106CBECA-0701-4FC3-838C-9DF816A19AE2} {2EDB3EB4-FA92-4BFF-B2D8-566584837231} = {106CBECA-0701-4FC3-838C-9DF816A19AE2} {48804216-2A0E-4168-A6D8-9CD068D14227} = {D1D6BC88-09AE-4FB4-AD24-5DED46A791DD} - {390AE700-B55F-4202-91EA-A822EB75B9BD} = {D1D6BC88-09AE-4FB4-AD24-5DED46A791DD} + {FF1D7936-842A-4BBB-8BEA-E9FE796DE700} = {D1D6BC88-09AE-4FB4-AD24-5DED46A791DD} {5043CECE-E6A7-4867-9CBE-02D27D83747A} = {4AFC9975-2456-4C70-94A4-84073C1CED93} + {459E0768-7EBD-4C41-BBA1-6DB3B3815E0A} = {470FBAF9-E1F8-4F3E-8786-198A1C81C8A8} + {5ABA70DE-3A3F-41F6-A1F5-D1F74F54F9BB} = {470FBAF9-E1F8-4F3E-8786-198A1C81C8A8} + {AC2857B4-103D-4D6D-9740-926EBF785042} = {470FBAF9-E1F8-4F3E-8786-198A1C81C8A8} + {470FBAF9-E1F8-4F3E-8786-198A1C81C8A8} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0} diff --git a/doc/devdocs/readme.md b/doc/devdocs/readme.md index 74419d2ae4..cbcc07d5ad 100644 --- a/doc/devdocs/readme.md +++ b/doc/devdocs/readme.md @@ -66,7 +66,12 @@ Various tools used by PowerToys. Includes the Visual Studio 2019 project templat 2. Visual Studio Community/Professional/Enterprise 2019 3. Once you've cloned and started the `PowerToys.sln`, in the solution explorer, if you see a dialog that says `install extra components`, click `install` -### Compile source code +**Optional step:**
+4. to build the Video Conference module, install the [WDK version 1903](https://docs.microsoft.com/en-us/windows-hardware/drivers/other-wdk-downloads) ([direct download link](https://go.microsoft.com/fwlink/?linkid=2085767))
+ During the installation, make sure that, when prompted, the `Install Windows Driver Kit Visual Studio extension` option is checked. + + +### Compiling Source Code - Open `PowerToys.sln` in Visual Studio, in the `Solutions Configuration` drop-down menu select `Release` or `Debug`, from the `Build` menu choose `Build Solution`. - The PowerToys binaries will be in your repo under `x64\Release\`. diff --git a/installer/PowerToysSetup/PowerToysSetup.wixproj b/installer/PowerToysSetup/PowerToysSetup.wixproj index f7816d7a41..cab87ecbf9 100644 --- a/installer/PowerToysSetup/PowerToysSetup.wixproj +++ b/installer/PowerToysSetup/PowerToysSetup.wixproj @@ -3,7 +3,7 @@ - Version=$(Version); + Version=$(Version) Release @@ -74,7 +74,6 @@ IF NOT DEFINED IsPipeline ( - call "$([MSBuild]::GetVsInstallRoot())\Common7\Tools\VsDevCmd.bat" -arch=amd64 -host_arch=amd64 -winsdk=10.0.18362.0 SET PTRoot=..\..\..\.. call "..\..\publish.cmd" diff --git a/installer/PowerToysSetup/Product.wxs b/installer/PowerToysSetup/Product.wxs index 3b7fde2275..1d85abba70 100644 --- a/installer/PowerToysSetup/Product.wxs +++ b/installer/PowerToysSetup/Product.wxs @@ -8,9 +8,11 @@ + + @@ -218,6 +220,10 @@ + + @@ -442,6 +448,9 @@ + + + @@ -637,6 +646,37 @@ + + + @@ -751,7 +791,7 @@ - + @@ -791,7 +831,7 @@ - + @@ -882,6 +922,9 @@ + + @@ -911,6 +954,7 @@ + diff --git a/installer/PowerToysSetupCustomActions/CustomAction.cpp b/installer/PowerToysSetupCustomActions/CustomAction.cpp index bf0e61835c..bb52d9c707 100644 --- a/installer/PowerToysSetupCustomActions/CustomAction.cpp +++ b/installer/PowerToysSetupCustomActions/CustomAction.cpp @@ -18,6 +18,7 @@ TRACELOGGING_DEFINE_PROVIDER( const DWORD USERNAME_DOMAIN_LEN = DNLEN + UNLEN + 2; // Domain Name + '\' + User Name + '\0' const DWORD USERNAME_LEN = UNLEN + 1; // User Name + '\0' +static const wchar_t* POWERTOYS_EXE_COMPONENT = L"{A2C66D91-3485-4D00-B04D-91844E6B345B}"; static const wchar_t* POWERTOYS_UPGRADE_CODE = L"{42B84BF7-5FBF-473B-9C8B-049DC16F7708}"; // Creates a Scheduled Task to run at logon for the current user. @@ -596,6 +597,165 @@ UINT __stdcall DetectPrevInstallPathCA(MSIHANDLE hInstall) return WcaFinalize(er); } +UINT __stdcall CertifyVirtualCameraDriverCA(MSIHANDLE hInstall) +{ +#ifdef CIBuild // On pipeline we are using microsoft certification + WcaInitialize(hInstall, "CertifyVirtualCameraDriverCA"); + return WcaFinalize(ERROR_SUCCESS); +#else + HRESULT hr = S_OK; + UINT er = ERROR_SUCCESS; + LPWSTR certificatePath = nullptr; + HCERTSTORE hCertStore = nullptr; + HANDLE hfile = nullptr; + DWORD size = INVALID_FILE_SIZE; + char * pFileContent = nullptr; + + hr = WcaInitialize(hInstall, "CertifyVirtualCameraDriverCA"); + ExitOnFailure(hr, "Failed to initialize", hr); + + hr = WcaGetProperty(L"CustomActionData", &certificatePath); + ExitOnFailure(hr, "Failed to get install preperty", hr); + + hCertStore = CertOpenStore(CERT_STORE_PROV_SYSTEM, 0, 0, CERT_SYSTEM_STORE_LOCAL_MACHINE, L"AuthRoot"); + if (!hCertStore) + { + hr = GetLastError(); + ExitOnFailure(hr, "Cannot put principal run level: %x", hr); + } + + hfile = CreateFile(certificatePath, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); + if (hfile == INVALID_HANDLE_VALUE) + { + hr = GetLastError(); + ExitOnFailure(hr, "Certificate file open failed", hr); + } + + size = GetFileSize(hfile, nullptr); + if (size == INVALID_FILE_SIZE) + { + hr = GetLastError(); + ExitOnFailure(hr, "Certificate file size not valid", hr); + } + + pFileContent = (char*)malloc(size); + + DWORD sizeread; + if (!ReadFile(hfile, pFileContent, size, &sizeread, nullptr)) + { + hr = GetLastError(); + ExitOnFailure(hr, "Certificate file read failed", hr); + } + + if (!CertAddEncodedCertificateToStore(hCertStore, + X509_ASN_ENCODING, + (const BYTE*)pFileContent, + size, + CERT_STORE_ADD_ALWAYS, + nullptr)) + { + hr = GetLastError(); + ExitOnFailure(hr, "Adding certificate failed", hr); + } + + free(pFileContent); + +LExit: + ReleaseStr(certificatePath); + if (hCertStore) + { + CertCloseStore(hCertStore, 0); + } + if (hfile) + { + CloseHandle(hfile); + } + + if (!SUCCEEDED(hr)) + { + PMSIHANDLE hRecord = MsiCreateRecord(0); + MsiRecordSetString(hRecord, 0, TEXT("Failed to add certificate to store")); + MsiProcessMessage(hInstall, INSTALLMESSAGE(INSTALLMESSAGE_WARNING + MB_OK), hRecord); + } + + + er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE; + return WcaFinalize(er); +#endif +} + + +UINT __stdcall InstallVirtualCameraDriverCA(MSIHANDLE hInstall) +{ + HRESULT hr = S_OK; + UINT er = ERROR_SUCCESS; + LPWSTR driverPath = nullptr; + + hr = WcaInitialize(hInstall, "InstallVirtualCameraDriverCA"); + ExitOnFailure(hr, "Failed to initialize"); + + hr = WcaGetProperty(L"CustomActionData", &driverPath); + ExitOnFailure(hr, "Failed to get install preperty"); + + BOOL requiresReboot; + DiInstallDriverW(GetConsoleWindow(), driverPath, DIIRFLAG_FORCE_INF, &requiresReboot); + + hr = GetLastError(); + ExitOnFailure(hr, "Failed to install driver"); + +LExit: + + if (!SUCCEEDED(hr)) + { + PMSIHANDLE hRecord = MsiCreateRecord(0); + MsiRecordSetString(hRecord, 0, TEXT("Failed to install virtual camera driver")); + MsiProcessMessage(hInstall, INSTALLMESSAGE(INSTALLMESSAGE_WARNING + MB_OK), hRecord); + } + + er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE; + return WcaFinalize(er); +} + +UINT __stdcall UninstallVirtualCameraDriverCA(MSIHANDLE hInstall) +{ + HRESULT hr = S_OK; + UINT er = ERROR_SUCCESS; + LPWSTR driverPath = nullptr; + + hr = WcaInitialize(hInstall, "UninstallVirtualCameraDriverCA"); + ExitOnFailure(hr, "Failed to initialize"); + + hr = WcaGetProperty(L"CustomActionData", &driverPath); + ExitOnFailure(hr, "Failed to get uninstall preperty"); + + BOOL requiresReboot; + DiUninstallDriverW(GetConsoleWindow(), driverPath, 0, &requiresReboot); + + switch (GetLastError()) + { + case ERROR_ACCESS_DENIED: + case ERROR_FILE_NOT_FOUND: + case ERROR_INVALID_FLAGS: + case ERROR_IN_WOW64: + { + hr = GetLastError(); + ExitOnFailure(hr, "Failed to uninstall driver"); + break; + } + } + +LExit: + + if (!SUCCEEDED(hr)) + { + PMSIHANDLE hRecord = MsiCreateRecord(0); + MsiRecordSetString(hRecord, 0, TEXT("Filed to iminstall virtual camera driver")); + MsiProcessMessage(hInstall, INSTALLMESSAGE(INSTALLMESSAGE_WARNING + MB_OK), hRecord); + } + + er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE; + return WcaFinalize(er); +} UINT __stdcall TerminateProcessesCA(MSIHANDLE hInstall) { diff --git a/installer/PowerToysSetupCustomActions/CustomAction.def b/installer/PowerToysSetupCustomActions/CustomAction.def index 3e8a2ab72e..06383fb65f 100644 --- a/installer/PowerToysSetupCustomActions/CustomAction.def +++ b/installer/PowerToysSetupCustomActions/CustomAction.def @@ -12,4 +12,8 @@ EXPORTS TelemetryLogUninstallFailCA TelemetryLogRepairCancelCA TelemetryLogRepairFailCA - TerminateProcessesCA \ No newline at end of file + TerminateProcessesCA + TerminateProcessesCA + CertifyVirtualCameraDriverCA + InstallVirtualCameraDriverCA + UninstallVirtualCameraDriverCA diff --git a/installer/PowerToysSetupCustomActions/PowerToysSetupCustomActions.vcxproj b/installer/PowerToysSetupCustomActions/PowerToysSetupCustomActions.vcxproj index 226a800022..6ad2af8a1c 100644 --- a/installer/PowerToysSetupCustomActions/PowerToysSetupCustomActions.vcxproj +++ b/installer/PowerToysSetupCustomActions/PowerToysSetupCustomActions.vcxproj @@ -56,7 +56,7 @@ $(WIX)sdk\$(WixPlatformToolset)\lib\x64;$(SolutionDir)\packages\WiX.3.11.2\tools\sdk\vs2017\lib\x64;..\..\$(PlatformShortName)\$(Configuration)\;%(AdditionalLibraryDirectories) - msi.lib;wcautil.lib;Psapi.lib;Pathcch.lib;comsupp.lib;taskschd.lib;Secur32.lib;msi.lib;dutil.lib;wcautil.lib;Version.lib;ApplicationUpdate.lib;Notifications.lib;Shlwapi.lib;%(AdditionalDependencies) + Newdev.lib;Crypt32.lib;msi.lib;wcautil.lib;Psapi.lib;Pathcch.lib;comsupp.lib;taskschd.lib;Secur32.lib;msi.lib;dutil.lib;wcautil.lib;Version.lib;ApplicationUpdate.lib;Notifications.lib;Shlwapi.lib;%(AdditionalDependencies) @@ -124,4 +124,4 @@ - \ No newline at end of file + diff --git a/installer/PowerToysSetupCustomActions/stdafx.h b/installer/PowerToysSetupCustomActions/stdafx.h index b53439bd31..61a3134864 100644 --- a/installer/PowerToysSetupCustomActions/stdafx.h +++ b/installer/PowerToysSetupCustomActions/stdafx.h @@ -4,6 +4,7 @@ #define DPSAPI_VERSION 1 // Windows Header Files: #include +#include #include #include #include diff --git a/src/modules/fancyzones/FancyZonesLib/FileWatcher.cpp b/src/common/SettingsAPI/FileWatcher.cpp similarity index 100% rename from src/modules/fancyzones/FancyZonesLib/FileWatcher.cpp rename to src/common/SettingsAPI/FileWatcher.cpp diff --git a/src/modules/fancyzones/FancyZonesLib/FileWatcher.h b/src/common/SettingsAPI/FileWatcher.h similarity index 74% rename from src/modules/fancyzones/FancyZonesLib/FileWatcher.h rename to src/common/SettingsAPI/FileWatcher.h index fd339578b9..206c1cd747 100644 --- a/src/modules/fancyzones/FancyZonesLib/FileWatcher.h +++ b/src/common/SettingsAPI/FileWatcher.h @@ -1,6 +1,13 @@ #pragma once -#include "pch.h" +#define WIN32_LEAN_AND_MEAN +#define NOMINMAX +#include + +#include +#include +#include +#include class FileWatcher { diff --git a/src/common/SettingsAPI/SetttingsAPI.vcxproj b/src/common/SettingsAPI/SetttingsAPI.vcxproj index 7a4a77284c..813c904374 100644 --- a/src/common/SettingsAPI/SetttingsAPI.vcxproj +++ b/src/common/SettingsAPI/SetttingsAPI.vcxproj @@ -28,10 +28,12 @@ + + Create diff --git a/src/common/SettingsAPI/settings_helpers.h b/src/common/SettingsAPI/settings_helpers.h index cd8f39432b..c5551a7f6b 100644 --- a/src/common/SettingsAPI/settings_helpers.h +++ b/src/common/SettingsAPI/settings_helpers.h @@ -8,6 +8,7 @@ namespace PTSettingsHelper { constexpr inline const wchar_t* log_settings_filename = L"log_settings.json"; + std::wstring get_powertoys_general_save_file_location(); std::wstring get_module_save_file_location(std::wstring_view powertoy_key); std::wstring get_module_save_folder_location(std::wstring_view powertoy_name); std::wstring get_root_save_folder_location(); diff --git a/src/common/Telemetry/ProjectTelemetry.h b/src/common/Telemetry/ProjectTelemetry.h index 1289d69201..fdf29dd694 100644 --- a/src/common/Telemetry/ProjectTelemetry.h +++ b/src/common/Telemetry/ProjectTelemetry.h @@ -1,6 +1,6 @@ #pragma once -#include -#include +#include "TraceLoggingProvider.h" +#include "TraceLoggingDefines.h" TRACELOGGING_DECLARE_PROVIDER(g_hProvider); diff --git a/src/common/interop/PowerToysInterop.vcxproj b/src/common/interop/PowerToysInterop.vcxproj index e395437c28..fb2d207fb9 100644 --- a/src/common/interop/PowerToysInterop.vcxproj +++ b/src/common/interop/PowerToysInterop.vcxproj @@ -47,6 +47,7 @@ + PowerToysInterop;%(PreprocessorDefinitions) NotUsing $(SolutionDir)src\common\interop;../../;../;%(AdditionalIncludeDirectories) false @@ -54,7 +55,7 @@ stdcpp17 - WindowsApp.lib;%(AdditionalDependencies) + Mf.lib;WindowsApp.lib;%(AdditionalDependencies) @@ -83,6 +84,8 @@ + + @@ -90,6 +93,12 @@ + + false + + + false + @@ -109,6 +118,12 @@ + + + + + + diff --git a/src/common/interop/PowerToysInterop.vcxproj.filters b/src/common/interop/PowerToysInterop.vcxproj.filters index 1a88f7682e..ddd7e81885 100644 --- a/src/common/interop/PowerToysInterop.vcxproj.filters +++ b/src/common/interop/PowerToysInterop.vcxproj.filters @@ -27,7 +27,13 @@ Header Files - + + Header Files + + + Header Files + + Header Files @@ -50,10 +56,16 @@ Source Files + + Source Files + + + Source Files + Resource Files - \ No newline at end of file + diff --git a/src/common/interop/interop.cpp b/src/common/interop/interop.cpp index 117af72122..09529e9d1c 100644 --- a/src/common/interop/interop.cpp +++ b/src/common/interop/interop.cpp @@ -13,12 +13,17 @@ // Therefore the simplest way is to compile these functions as native using the pragmas below. #pragma managed(push, off) #include "../utils/os-detect.h" +// TODO: move to a separate library in common +#include "../../modules/videoconference/VideoConferenceShared/MicrophoneDevice.h" +#include "../../modules/videoconference/VideoConferenceShared/VideoCaptureDeviceList.h" #pragma managed(pop) #include + using namespace System; using namespace System::Runtime::InteropServices; +using System::Collections::Generic::List; // https://docs.microsoft.com/en-us/cpp/dotnet/how-to-wrap-native-class-for-use-by-csharp?view=vs-2019 namespace interop @@ -122,6 +127,32 @@ public static String ^ GetProductVersion() { return gcnew String(get_product_version().c_str()); } + + static List ^ GetAllActiveMicrophoneDeviceNames() { + auto names = gcnew List(); + for (const auto& device : MicrophoneDevice::getAllActive()) + { + names->Add(gcnew String(device.name().data())); + } + return names; + } + + static List ^ + GetAllVideoCaptureDeviceNames() { + auto names = gcnew List(); + VideoCaptureDeviceList vcdl; + vcdl.EnumerateDevices(); + + for (UINT32 i = 0; i < vcdl.Count(); ++i) + { + auto name = gcnew String(vcdl.GetDeviceName(i).data()); + if (name != L"PowerToys VideoConference Mute") + { + names->Add(name); + } + } + return names; + } }; public diff --git a/src/common/interop/packages.config b/src/common/interop/packages.config new file mode 100644 index 0000000000..3e9434647c --- /dev/null +++ b/src/common/interop/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/modules/fancyzones/FancyZonesLib/FancyZones.cpp b/src/modules/fancyzones/FancyZonesLib/FancyZones.cpp index 45b8dc0ad3..1574c7f247 100644 --- a/src/modules/fancyzones/FancyZonesLib/FancyZones.cpp +++ b/src/modules/fancyzones/FancyZonesLib/FancyZones.cpp @@ -7,13 +7,13 @@ #include #include #include +#include #include "FancyZones.h" #include "FancyZonesLib/Settings.h" #include "FancyZonesLib/ZoneWindow.h" #include "FancyZonesLib/FancyZonesData.h" #include "FancyZonesLib/ZoneSet.h" -#include "FancyZonesLib/FileWatcher.h" #include "FancyZonesLib/WindowMoveHandler.h" #include "FancyZonesLib/FancyZonesWinHookEventIDs.h" #include "FancyZonesLib/util.h" diff --git a/src/modules/fancyzones/FancyZonesLib/FancyZonesLib.vcxproj b/src/modules/fancyzones/FancyZonesLib/FancyZonesLib.vcxproj index 0ac31d0179..3100570499 100644 --- a/src/modules/fancyzones/FancyZonesLib/FancyZonesLib.vcxproj +++ b/src/modules/fancyzones/FancyZonesLib/FancyZonesLib.vcxproj @@ -41,7 +41,6 @@ - @@ -67,7 +66,6 @@ - diff --git a/src/modules/fancyzones/FancyZonesLib/FancyZonesLib.vcxproj.filters b/src/modules/fancyzones/FancyZonesLib/FancyZonesLib.vcxproj.filters index b3c04c2c80..3bcfab7d1d 100644 --- a/src/modules/fancyzones/FancyZonesLib/FancyZonesLib.vcxproj.filters +++ b/src/modules/fancyzones/FancyZonesLib/FancyZonesLib.vcxproj.filters @@ -78,9 +78,6 @@ Header Files - - Header Files - Header Files @@ -140,9 +137,6 @@ Source Files - - Source Files - Source Files diff --git a/src/modules/videoconference/VideoConferenceModule/Icons/Off-NotInUse Dark.png b/src/modules/videoconference/VideoConferenceModule/Icons/Off-NotInUse Dark.png new file mode 100644 index 0000000000..4070bca9e3 Binary files /dev/null and b/src/modules/videoconference/VideoConferenceModule/Icons/Off-NotInUse Dark.png differ diff --git a/src/modules/videoconference/VideoConferenceModule/Icons/Off-NotInUse Dark.svg b/src/modules/videoconference/VideoConferenceModule/Icons/Off-NotInUse Dark.svg new file mode 100644 index 0000000000..4860bbfd3a --- /dev/null +++ b/src/modules/videoconference/VideoConferenceModule/Icons/Off-NotInUse Dark.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + Camera not in use + + + Microphone off + + diff --git a/src/modules/videoconference/VideoConferenceModule/Icons/Off-NotInUse Light.png b/src/modules/videoconference/VideoConferenceModule/Icons/Off-NotInUse Light.png new file mode 100644 index 0000000000..6962f0115a Binary files /dev/null and b/src/modules/videoconference/VideoConferenceModule/Icons/Off-NotInUse Light.png differ diff --git a/src/modules/videoconference/VideoConferenceModule/Icons/Off-NotInUse Light.svg b/src/modules/videoconference/VideoConferenceModule/Icons/Off-NotInUse Light.svg new file mode 100644 index 0000000000..a30e909ff1 --- /dev/null +++ b/src/modules/videoconference/VideoConferenceModule/Icons/Off-NotInUse Light.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + Camera not in use + + + Microphone off + + diff --git a/src/modules/videoconference/VideoConferenceModule/Icons/Off-Off Dark.png b/src/modules/videoconference/VideoConferenceModule/Icons/Off-Off Dark.png new file mode 100644 index 0000000000..b0ce502a4d Binary files /dev/null and b/src/modules/videoconference/VideoConferenceModule/Icons/Off-Off Dark.png differ diff --git a/src/modules/videoconference/VideoConferenceModule/Icons/Off-Off Dark.svg b/src/modules/videoconference/VideoConferenceModule/Icons/Off-Off Dark.svg new file mode 100644 index 0000000000..c147f2278e --- /dev/null +++ b/src/modules/videoconference/VideoConferenceModule/Icons/Off-Off Dark.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + Camera off + + + Microphone off + + diff --git a/src/modules/videoconference/VideoConferenceModule/Icons/Off-Off Light.png b/src/modules/videoconference/VideoConferenceModule/Icons/Off-Off Light.png new file mode 100644 index 0000000000..500386523f Binary files /dev/null and b/src/modules/videoconference/VideoConferenceModule/Icons/Off-Off Light.png differ diff --git a/src/modules/videoconference/VideoConferenceModule/Icons/Off-Off Light.svg b/src/modules/videoconference/VideoConferenceModule/Icons/Off-Off Light.svg new file mode 100644 index 0000000000..25eecdd72f --- /dev/null +++ b/src/modules/videoconference/VideoConferenceModule/Icons/Off-Off Light.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + Camera off + + + Microphone off + + diff --git a/src/modules/videoconference/VideoConferenceModule/Icons/Off-On Dark.png b/src/modules/videoconference/VideoConferenceModule/Icons/Off-On Dark.png new file mode 100644 index 0000000000..ba27ff46d4 Binary files /dev/null and b/src/modules/videoconference/VideoConferenceModule/Icons/Off-On Dark.png differ diff --git a/src/modules/videoconference/VideoConferenceModule/Icons/Off-On Dark.svg b/src/modules/videoconference/VideoConferenceModule/Icons/Off-On Dark.svg new file mode 100644 index 0000000000..ea31e3d78d --- /dev/null +++ b/src/modules/videoconference/VideoConferenceModule/Icons/Off-On Dark.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + Camera on + + Microphone off + + + diff --git a/src/modules/videoconference/VideoConferenceModule/Icons/Off-On Light.png b/src/modules/videoconference/VideoConferenceModule/Icons/Off-On Light.png new file mode 100644 index 0000000000..abc8c972a2 Binary files /dev/null and b/src/modules/videoconference/VideoConferenceModule/Icons/Off-On Light.png differ diff --git a/src/modules/videoconference/VideoConferenceModule/Icons/Off-On Light.svg b/src/modules/videoconference/VideoConferenceModule/Icons/Off-On Light.svg new file mode 100644 index 0000000000..e3c0352bcc --- /dev/null +++ b/src/modules/videoconference/VideoConferenceModule/Icons/Off-On Light.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + Camera on + + Microphone off + + + diff --git a/src/modules/videoconference/VideoConferenceModule/Icons/On-NotInUse Dark.png b/src/modules/videoconference/VideoConferenceModule/Icons/On-NotInUse Dark.png new file mode 100644 index 0000000000..928c8f1e50 Binary files /dev/null and b/src/modules/videoconference/VideoConferenceModule/Icons/On-NotInUse Dark.png differ diff --git a/src/modules/videoconference/VideoConferenceModule/Icons/On-NotInUse Dark.svg b/src/modules/videoconference/VideoConferenceModule/Icons/On-NotInUse Dark.svg new file mode 100644 index 0000000000..f2b667444e --- /dev/null +++ b/src/modules/videoconference/VideoConferenceModule/Icons/On-NotInUse Dark.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + Camera not in use + + Microphone on + + + diff --git a/src/modules/videoconference/VideoConferenceModule/Icons/On-NotInUse Light.png b/src/modules/videoconference/VideoConferenceModule/Icons/On-NotInUse Light.png new file mode 100644 index 0000000000..6a4662711b Binary files /dev/null and b/src/modules/videoconference/VideoConferenceModule/Icons/On-NotInUse Light.png differ diff --git a/src/modules/videoconference/VideoConferenceModule/Icons/On-NotInUse Light.svg b/src/modules/videoconference/VideoConferenceModule/Icons/On-NotInUse Light.svg new file mode 100644 index 0000000000..9ce95b772a --- /dev/null +++ b/src/modules/videoconference/VideoConferenceModule/Icons/On-NotInUse Light.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + Camera not in use + + Microphone on + + + diff --git a/src/modules/videoconference/VideoConferenceModule/Icons/On-Off Dark.png b/src/modules/videoconference/VideoConferenceModule/Icons/On-Off Dark.png new file mode 100644 index 0000000000..d8fcd23cdc Binary files /dev/null and b/src/modules/videoconference/VideoConferenceModule/Icons/On-Off Dark.png differ diff --git a/src/modules/videoconference/VideoConferenceModule/Icons/On-Off Dark.svg b/src/modules/videoconference/VideoConferenceModule/Icons/On-Off Dark.svg new file mode 100644 index 0000000000..67080eabdd --- /dev/null +++ b/src/modules/videoconference/VideoConferenceModule/Icons/On-Off Dark.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + Camera off + + Microphone on + + + diff --git a/src/modules/videoconference/VideoConferenceModule/Icons/On-Off Light.png b/src/modules/videoconference/VideoConferenceModule/Icons/On-Off Light.png new file mode 100644 index 0000000000..271809ed7a Binary files /dev/null and b/src/modules/videoconference/VideoConferenceModule/Icons/On-Off Light.png differ diff --git a/src/modules/videoconference/VideoConferenceModule/Icons/On-Off Light.svg b/src/modules/videoconference/VideoConferenceModule/Icons/On-Off Light.svg new file mode 100644 index 0000000000..f5661655bc --- /dev/null +++ b/src/modules/videoconference/VideoConferenceModule/Icons/On-Off Light.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + Camera off + + Microphone on + + + diff --git a/src/modules/videoconference/VideoConferenceModule/Icons/On-On Dark.png b/src/modules/videoconference/VideoConferenceModule/Icons/On-On Dark.png new file mode 100644 index 0000000000..52f4611883 Binary files /dev/null and b/src/modules/videoconference/VideoConferenceModule/Icons/On-On Dark.png differ diff --git a/src/modules/videoconference/VideoConferenceModule/Icons/On-On Dark.svg b/src/modules/videoconference/VideoConferenceModule/Icons/On-On Dark.svg new file mode 100644 index 0000000000..e539e785a6 --- /dev/null +++ b/src/modules/videoconference/VideoConferenceModule/Icons/On-On Dark.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + Camera on + + Microphone on + + + diff --git a/src/modules/videoconference/VideoConferenceModule/Icons/On-On Light.png b/src/modules/videoconference/VideoConferenceModule/Icons/On-On Light.png new file mode 100644 index 0000000000..3fa2959951 Binary files /dev/null and b/src/modules/videoconference/VideoConferenceModule/Icons/On-On Light.png differ diff --git a/src/modules/videoconference/VideoConferenceModule/Icons/On-On Light.svg b/src/modules/videoconference/VideoConferenceModule/Icons/On-On Light.svg new file mode 100644 index 0000000000..ff4a5c2e31 --- /dev/null +++ b/src/modules/videoconference/VideoConferenceModule/Icons/On-On Light.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + Camera on + + Microphone on + + + diff --git a/src/modules/videoconference/VideoConferenceModule/README.md b/src/modules/videoconference/VideoConferenceModule/README.md new file mode 100644 index 0000000000..d53920685a --- /dev/null +++ b/src/modules/videoconference/VideoConferenceModule/README.md @@ -0,0 +1,14 @@ +# Video Conference Mute + +# Introduction +The Video Conference Mute module allows muting microphone and/or web camera video stream during video calls or other activity. + +# Usage +If you'd like to mute your web camera, please select "PowerToys VideoConference Mute" device in your web camera-using app, then restart it. + +During a video call, you can use default shortcuts to mute microphone, web camera or both. You'll see a toolbar indicating corresponding mute statuses. + +# Options +You can tweak the toolbar position on the screen as well as set web camera overlay image during muting. + +# Backlog diff --git a/src/modules/videoconference/VideoConferenceModule/Toolbar.cpp b/src/modules/videoconference/VideoConferenceModule/Toolbar.cpp new file mode 100644 index 0000000000..5082298020 --- /dev/null +++ b/src/modules/videoconference/VideoConferenceModule/Toolbar.cpp @@ -0,0 +1,335 @@ +#include "pch.h" +#include "Toolbar.h" + +#include + +#include + +#include "Logging.h" +#include "VideoConferenceModule.h" + +Toolbar* toolbar = nullptr; + +const int REFRESH_RATE = 100; +const int OVERLAY_SHOW_TIME = 500; +const int BORDER_OFFSET = 12; + +Toolbar::Toolbar() +{ + toolbar = this; + darkImages.camOnMicOn = Gdiplus::Image::FromFile(L"modules/VideoConference/Icons/On-On Dark.png"); + darkImages.camOffMicOn = Gdiplus::Image::FromFile(L"modules/VideoConference/Icons/On-Off Dark.png"); + darkImages.camOnMicOff = Gdiplus::Image::FromFile(L"modules/VideoConference/Icons/Off-On Dark.png"); + darkImages.camOffMicOff = Gdiplus::Image::FromFile(L"modules/VideoConference/Icons/Off-Off Dark.png"); + darkImages.camUnusedMicOn = Gdiplus::Image::FromFile(L"modules/VideoConference/Icons/On-NotInUse Dark.png"); + darkImages.camUnusedMicOff = Gdiplus::Image::FromFile(L"modules/VideoConference/Icons/Off-NotInUse Dark.png"); + + lightImages.camOnMicOn = Gdiplus::Image::FromFile(L"modules/VideoConference/Icons/On-On Light.png"); + lightImages.camOffMicOn = Gdiplus::Image::FromFile(L"modules/VideoConference/Icons/On-Off Light.png"); + lightImages.camOnMicOff = Gdiplus::Image::FromFile(L"modules/VideoConference/Icons/Off-On Light.png"); + lightImages.camOffMicOff = Gdiplus::Image::FromFile(L"modules/VideoConference/Icons/Off-Off Light.png"); + lightImages.camUnusedMicOn = Gdiplus::Image::FromFile(L"modules/VideoConference/Icons/On-NotInUse Light.png"); + lightImages.camUnusedMicOff = Gdiplus::Image::FromFile(L"modules/VideoConference/Icons/Off-NotInUse Light.png"); +} + +void Toolbar::scheduleModuleSettingsUpdate() +{ + moduleSettingsUpdateScheduled = true; +} + +void Toolbar::scheduleGeneralSettingsUpdate() +{ + generalSettingsUpdateScheduled = true; +} + +LRESULT Toolbar::WindowProcessMessages(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) +{ + switch (msg) + { + case WM_DESTROY: + return 0; + case WM_LBUTTONDOWN: + { + int x = GET_X_LPARAM(lparam); + int y = GET_Y_LPARAM(lparam); + + if (x < 322 / 2) + { + VideoConferenceModule::reverseMicrophoneMute(); + } + else + { + VideoConferenceModule::reverseVirtualCameraMuteState(); + } + + return DefWindowProcW(hwnd, msg, wparam, lparam); + } + case WM_CREATE: + case WM_PAINT: + { + PAINTSTRUCT ps; + HDC hdc; + + hdc = BeginPaint(hwnd, &ps); + + Gdiplus::Graphics graphic(hdc); + + ToolbarImages* themeImages = &toolbar->darkImages; + + if (toolbar->theme == L"light" || (toolbar->theme == L"system" && !WindowsColors::is_dark_mode())) + { + themeImages = &toolbar->lightImages; + } + else + { + themeImages = &toolbar->darkImages; + } + Gdiplus::Image* toolbarImage = nullptr; + if (!toolbar->cameraInUse) + { + if (toolbar->microphoneMuted) + { + toolbarImage = themeImages->camUnusedMicOff; + } + else + { + toolbarImage = themeImages->camUnusedMicOn; + } + } + else if (toolbar->microphoneMuted) + { + if (toolbar->cameraMuted) + { + toolbarImage = themeImages->camOffMicOff; + } + else + { + toolbarImage = themeImages->camOnMicOff; + } + } + else + { + if (toolbar->cameraMuted) + { + toolbarImage = themeImages->camOffMicOn; + } + else + { + toolbarImage = themeImages->camOnMicOn; + } + } + graphic.DrawImage(toolbarImage, 0, 0, toolbarImage->GetWidth(), toolbarImage->GetHeight()); + + EndPaint(hwnd, &ps); + break; + } + case WM_TIMER: + { + if (toolbar->generalSettingsUpdateScheduled) + { + instance->onGeneralSettingsChanged(); + toolbar->generalSettingsUpdateScheduled = false; + } + if (toolbar->moduleSettingsUpdateScheduled) + { + instance->onModuleSettingsChanged(); + toolbar->moduleSettingsUpdateScheduled = false; + } + + toolbar->cameraInUse = VideoConferenceModule::getVirtualCameraInUse(); + + InvalidateRect(hwnd, NULL, NULL); + + using namespace std::chrono; + const auto nowMillis = duration_cast(system_clock::now().time_since_epoch()).count(); + const bool showOverlayTimeout = nowMillis - toolbar->lastTimeCamOrMicMuteStateChanged > OVERLAY_SHOW_TIME; + + static bool previousShow = false; + bool show = false; + + if (toolbar->cameraInUse) + { + show = toolbar->HideToolbarWhenUnmuted ? toolbar->microphoneMuted || toolbar->cameraMuted : true; + } + else if (toolbar->previouscameraInUse) + { + VideoConferenceModule::unmuteAll(); + } + else + { + show = toolbar->microphoneMuted; + } + show = show || !showOverlayTimeout; + if (show) + { + ShowWindow(hwnd, SW_SHOW); + } + else + { + ShowWindow(hwnd, SW_HIDE); + } + if (previousShow != show) + { + previousShow = show; + LOG(show ? "Toolbar visibility changed to shown" : "Toolbar visibility changed to hidden"); + } + + KillTimer(hwnd, toolbar->nTimerId); + toolbar->previouscameraInUse = toolbar->cameraInUse; + break; + } + default: + return DefWindowProcW(hwnd, msg, wparam, lparam); + } + + toolbar->nTimerId = SetTimer(hwnd, 101, REFRESH_RATE, nullptr); + + return DefWindowProcW(hwnd, msg, wparam, lparam); +} + +void Toolbar::show(std::wstring position, std::wstring monitorString) +{ + for (auto& hwnd : hwnds) + { + PostMessageW(hwnd, WM_CLOSE, 0, 0); + } + hwnds.clear(); + + int overlayWidth = darkImages.camOffMicOff->GetWidth(); + int overlayHeight = darkImages.camOffMicOff->GetHeight(); + + // Register the window class + LPCWSTR CLASS_NAME = L"MuteNotificationWindowClass"; + WNDCLASS wc{}; + wc.hInstance = GetModuleHandleW(nullptr); + wc.lpszClassName = CLASS_NAME; + wc.hCursor = LoadCursor(nullptr, IDC_ARROW); + wc.hbrBackground = (HBRUSH)COLOR_WINDOW; + wc.lpfnWndProc = WindowProcessMessages; + RegisterClassW(&wc); + + // Create the window + DWORD dwExtStyle = 0; + DWORD dwStyle = WS_POPUPWINDOW; + + std::vector monitorInfos; + + if (monitorString == L"All monitors") + { + monitorInfos = MonitorInfo::GetMonitors(false); + } + else //"Main monitor" or non-present + { + monitorInfos.push_back(MonitorInfo::GetPrimaryMonitor()); + } + + for (auto& monitorInfo : monitorInfos) + { + int positionX = 0; + int positionY = 0; + + if (position == L"Top left corner") + { + positionX = monitorInfo.left() + BORDER_OFFSET; + positionY = monitorInfo.top() + BORDER_OFFSET; + } + else if (position == L"Top center") + { + positionX = monitorInfo.middle().x - overlayWidth / 2; + positionY = monitorInfo.top() + BORDER_OFFSET; + } + else if (position == L"Bottom left corner") + { + positionX = monitorInfo.left() + BORDER_OFFSET; + positionY = monitorInfo.bottom() - overlayHeight - BORDER_OFFSET; + } + else if (position == L"Bottom center") + { + positionX = monitorInfo.middle().x - overlayWidth / 2; + positionY = monitorInfo.bottom() - overlayHeight - BORDER_OFFSET; + } + else if (position == L"Bottom right corner") + { + positionX = monitorInfo.right() - overlayWidth - BORDER_OFFSET; + positionY = monitorInfo.bottom() - overlayHeight - BORDER_OFFSET; + } + else //"Top right corner" or non-present + { + positionX = monitorInfo.right() - overlayWidth - BORDER_OFFSET; + positionY = monitorInfo.top() + BORDER_OFFSET; + } + + HWND hwnd; + hwnd = CreateWindowExW( + WS_EX_TOOLWINDOW | WS_EX_LAYERED, + CLASS_NAME, + CLASS_NAME, + WS_POPUP, + positionX, + positionY, + overlayWidth, + overlayHeight, + nullptr, + nullptr, + GetModuleHandleW(nullptr), + nullptr); + + auto transparrentColorKey = RGB(0, 0, 255); + HBRUSH brush = CreateSolidBrush(transparrentColorKey); + SetClassLongPtr(hwnd, GCLP_HBRBACKGROUND, (LONG_PTR)brush); + + SetLayeredWindowAttributes(hwnd, transparrentColorKey, 0, LWA_COLORKEY); + + SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); + + hwnds.push_back(hwnd); + } +} + +void Toolbar::hide() +{ + for (auto& hwnd : hwnds) + { + PostMessage(hwnd, WM_CLOSE, 0, 0); + } + hwnds.clear(); +} + +bool Toolbar::getCameraMute() +{ + return cameraMuted; +} + +void Toolbar::setCameraMute(bool mute) +{ + if (mute != cameraMuted) + { + lastTimeCamOrMicMuteStateChanged = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + } + cameraMuted = mute; +} + +bool Toolbar::getMicrophoneMute() +{ + return microphoneMuted; +} + +void Toolbar::setMicrophoneMute(bool mute) +{ + if (mute != microphoneMuted) + { + lastTimeCamOrMicMuteStateChanged = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + } + + microphoneMuted = mute; +} + +void Toolbar::setHideToolbarWhenUnmuted(bool hide) +{ + HideToolbarWhenUnmuted = hide; +} + +void Toolbar::setTheme(std::wstring theme) +{ + Toolbar::theme = theme; +} diff --git a/src/modules/videoconference/VideoConferenceModule/Toolbar.h b/src/modules/videoconference/VideoConferenceModule/Toolbar.h new file mode 100644 index 0000000000..7bdc86f105 --- /dev/null +++ b/src/modules/videoconference/VideoConferenceModule/Toolbar.h @@ -0,0 +1,61 @@ +#pragma once + +#include +#include +#include + +#include + +struct ToolbarImages +{ + Gdiplus::Image* camOnMicOn = nullptr; + Gdiplus::Image* camOffMicOn = nullptr; + Gdiplus::Image* camOnMicOff = nullptr; + Gdiplus::Image* camOffMicOff = nullptr; + Gdiplus::Image* camUnusedMicOn = nullptr; + Gdiplus::Image* camUnusedMicOff = nullptr; +}; + +class Toolbar +{ +public: + Toolbar(); + + void scheduleModuleSettingsUpdate(); + void scheduleGeneralSettingsUpdate(); + + void show(std::wstring position, std::wstring monitorString); + void hide(); + + bool getCameraMute(); + void setCameraMute(bool mute); + bool getMicrophoneMute(); + void setMicrophoneMute(bool mute); + + void setTheme(std::wstring theme); + void setHideToolbarWhenUnmuted(bool hide); + +private: + static LRESULT CALLBACK WindowProcessMessages(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam); + + // Window callback can't be non-static so this members can't as well + std::vector hwnds; + + ToolbarImages darkImages; + ToolbarImages lightImages; + + bool cameraMuted = false; + bool cameraInUse = false; + bool previouscameraInUse = false; + bool microphoneMuted = false; + + std::wstring theme = L"system"; + + bool HideToolbarWhenUnmuted = true; + + uint64_t lastTimeCamOrMicMuteStateChanged; + + std::atomic_bool moduleSettingsUpdateScheduled = false; + std::atomic_bool generalSettingsUpdateScheduled = false; + UINT_PTR nTimerId; +}; diff --git a/src/modules/videoconference/VideoConferenceModule/Video Conference.filters b/src/modules/videoconference/VideoConferenceModule/Video Conference.filters new file mode 100644 index 0000000000..e19316f1bd --- /dev/null +++ b/src/modules/videoconference/VideoConferenceModule/Video Conference.filters @@ -0,0 +1,55 @@ + + + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + + {2c7c97f7-0d87-4230-a4b2-baf2cfc35d58} + + + {aa4b6713-589d-42ef-804d-3a045833f83f} + + + + + + + + + \ No newline at end of file diff --git a/src/modules/videoconference/VideoConferenceModule/Video Conference.vcxproj b/src/modules/videoconference/VideoConferenceModule/Video Conference.vcxproj new file mode 100644 index 0000000000..425313bce1 --- /dev/null +++ b/src/modules/videoconference/VideoConferenceModule/Video Conference.vcxproj @@ -0,0 +1,183 @@ + + + + + + Debug + x64 + + + Release + x64 + + + + 15.0 + {5ABA70DE-3A3F-41F6-A1F5-D1F74F54F9BB} + Win32Proj + overlaywindow + 10.0.18362.0 + VideoConferenceModule + + + + DynamicLibrary + true + v142 + Unicode + Spectre + + + DynamicLibrary + false + v142 + true + Unicode + Spectre + + + + + + + + + + + + + + + + false + $(SolutionDir)$(Platform)\$(Configuration)\modules\VideoConference\ + $(SolutionDir)$(Platform)\$(Configuration)\obj\$(ProjectName)\ + + + true + $(SolutionDir)$(Platform)\$(Configuration)\modules\VideoConference\ + $(SolutionDir)$(Platform)\$(Configuration)\obj\$(ProjectName)\ + + + + Use + Level3 + MaxSpeed + true + true + true + NDEBUG;OVERLAYWINDOW_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + pch.h + MultiThreaded + ..\..\..\;..\..\;..\VideoConferenceShared\;%(AdditionalIncludeDirectories) + stdcpplatest + + + Windows + true + true + true + $(OutDir)$(TargetName)$(TargetExt) + mfplat.lib;mf.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;gdiplus.lib;dwmapi.lib;uxtheme.lib;shcore.lib;Wtsapi32.lib;%(AdditionalDependencies) + + + xcopy /y /I "$(ProjectDir)Icons\*" "$(OutDir)Icons" +xcopy /y /I "$(ProjectDir)black.bmp*" "$(OutDir)" + + + + + Use + Level3 + Disabled + true + _DEBUG;OVERLAYWINDOW_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + pch.h + MultiThreadedDebug + ..\..\..\common\inc;..\..\..\common\Telemetry;..\..\..\;..\..\;..\VideoConferenceShared\;%(AdditionalIncludeDirectories) + stdcpplatest + + + Windows + true + $(OutDir)$(TargetName)$(TargetExt) + mfplat.lib;mf.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;gdiplus.lib;dwmapi.lib;uxtheme.lib;shcore.lib;Wtsapi32.lib;dxguid.lib;%(AdditionalDependencies) + + + xcopy /y /I "$(ProjectDir)Icons\*" "$(OutDir)Icons" +xcopy /y /I "$(ProjectDir)black.bmp*" "$(OutDir)" + + + + + + + + + + + + + + + Create + Create + + + + + + {459e0768-7ebd-4c41-bba1-6db3b3815e0a} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + + + + {caba8dfb-823b-4bf2-93ac-3f31984150d9} + + + {6955446d-23f7-4023-9bb3-8657f904af99} + + + {98537082-0fdb-40de-abd8-0dc5a4269bab} + + + \ No newline at end of file diff --git a/src/modules/videoconference/VideoConferenceModule/Video Conference.vcxproj.filters b/src/modules/videoconference/VideoConferenceModule/Video Conference.vcxproj.filters new file mode 100644 index 0000000000..014fce2e73 --- /dev/null +++ b/src/modules/videoconference/VideoConferenceModule/Video Conference.vcxproj.filters @@ -0,0 +1,100 @@ + + + + + + + + + + + + + + + + + + + + Icons + + + Icons + + + Icons + + + Icons + + + Icons + + + Icons + + + Icons + + + Icons + + + Icons + + + Icons + + + Icons + + + Icons + + + + + + Icons + + + Icons + + + Icons + + + Icons + + + Icons + + + Icons + + + Icons + + + Icons + + + Icons + + + Icons + + + Icons + + + Icons + + + + + {735361e2-82fa-4034-b9c9-cd6aa099eaa5} + + + \ No newline at end of file diff --git a/src/modules/videoconference/VideoConferenceModule/VideoConferenceModule.cpp b/src/modules/videoconference/VideoConferenceModule/VideoConferenceModule.cpp new file mode 100644 index 0000000000..b310315648 --- /dev/null +++ b/src/modules/videoconference/VideoConferenceModule/VideoConferenceModule.cpp @@ -0,0 +1,568 @@ +#include "pch.h" + +#include "VideoConferenceModule.h" + +#include + +#include +#include + +#include + +#include +#include +#include +#include + +#include + +#include "logging.h" +#include "trace.h" + +extern "C" IMAGE_DOS_HEADER __ImageBase; + +VideoConferenceModule* instance = nullptr; + +VideoConferenceSettings VideoConferenceModule::settings; +Toolbar VideoConferenceModule::toolbar; + +HHOOK VideoConferenceModule::hook_handle; + +IAudioEndpointVolume* endpointVolume = NULL; + +bool VideoConferenceModule::isKeyPressed(unsigned int keyCode) +{ + return (GetKeyState(keyCode) & 0x8000); +} + +namespace fs = std::filesystem; + +bool VideoConferenceModule::isHotkeyPressed(DWORD code, PowerToysSettings::HotkeyObject& hotkey) +{ + return code == hotkey.get_code() && + isKeyPressed(VK_SHIFT) == hotkey.shift_pressed() && + isKeyPressed(VK_CONTROL) == hotkey.ctrl_pressed() && + isKeyPressed(VK_LWIN) == hotkey.win_pressed() && + (isKeyPressed(VK_LMENU)) == hotkey.alt_pressed(); +} + +void VideoConferenceModule::reverseMicrophoneMute() +{ + bool muted = false; + for (auto& controlledMic : instance->_controlledMicrophones) + { + const bool was_muted = controlledMic.muted(); + controlledMic.toggle_muted(); + muted = muted || !was_muted; + } + if (muted) + { + Trace::MicrophoneMuted(); + } + toolbar.setMicrophoneMute(muted); +} + +bool VideoConferenceModule::getMicrophoneMuteState() +{ + return instance->_microphoneTrackedInUI ? instance->_microphoneTrackedInUI->muted() : false; +} + +void VideoConferenceModule::reverseVirtualCameraMuteState() +{ + bool muted = false; + if (!instance->_settingsUpdateChannel.has_value()) + { + return; + } + + instance->_settingsUpdateChannel->access([&muted](auto settingsMemory) { + auto settings = reinterpret_cast(settingsMemory._data); + settings->useOverlayImage = !settings->useOverlayImage; + muted = settings->useOverlayImage; + }); + + if (muted) + { + Trace::CameraMuted(); + } + toolbar.setCameraMute(muted); +} + +bool VideoConferenceModule::getVirtualCameraMuteState() +{ + bool disabled = false; + if (!instance->_settingsUpdateChannel.has_value()) + { + return disabled; + } + instance->_settingsUpdateChannel->access([&disabled](auto settingsMemory) { + auto settings = reinterpret_cast(settingsMemory._data); + disabled = settings->useOverlayImage; + }); + return disabled; +} + +bool VideoConferenceModule::getVirtualCameraInUse() +{ + if (!instance->_settingsUpdateChannel.has_value()) + { + return false; + } + bool inUse = false; + instance->_settingsUpdateChannel->access([&inUse](auto settingsMemory) { + auto settings = reinterpret_cast(settingsMemory._data); + inUse = settings->cameraInUse; + }); + return inUse; +} + +LRESULT CALLBACK VideoConferenceModule::LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) +{ + if (nCode == HC_ACTION) + { + switch (wParam) + { + case WM_KEYDOWN: + KBDLLHOOKSTRUCT* kbd = reinterpret_cast(lParam); + + if (isHotkeyPressed(kbd->vkCode, settings.cameraAndMicrophoneMuteHotkey)) + { + const bool cameraInUse = getVirtualCameraInUse(); + const bool microphoneIsMuted = getMicrophoneMuteState(); + const bool cameraIsMuted = cameraInUse && getVirtualCameraMuteState(); + if (cameraInUse) + { + // we're likely on a video call, so we must mute the unmuted cam/mic or reverse the mute state + // of everything, if cam and mic mute states are the same + if (microphoneIsMuted == cameraIsMuted) + { + reverseMicrophoneMute(); + reverseVirtualCameraMuteState(); + } + else if (cameraIsMuted) + { + reverseMicrophoneMute(); + } + else if (microphoneIsMuted) + { + reverseVirtualCameraMuteState(); + } + } + else + { + // if the camera is not in use, we just mute/unmute the mic + reverseMicrophoneMute(); + } + return 1; + } + else if (isHotkeyPressed(kbd->vkCode, settings.microphoneMuteHotkey)) + { + reverseMicrophoneMute(); + return 1; + } + else if (isHotkeyPressed(kbd->vkCode, settings.cameraMuteHotkey)) + { + reverseVirtualCameraMuteState(); + return 1; + } + } + } + + return CallNextHookEx(hook_handle, nCode, wParam, lParam); +} + +void VideoConferenceModule::onGeneralSettingsChanged() +{ + auto settings = PTSettingsHelper::load_general_settings(); + bool enabled = false; + try + { + if (json::has(settings, L"enabled")) + { + for (const auto& mod : settings.GetNamedObject(L"enabled")) + { + const auto value = mod.Value(); + if (value.ValueType() != json::JsonValueType::Boolean) + { + continue; + } + if (mod.Key() == get_key()) + { + enabled = value.GetBoolean(); + break; + } + } + } + } + catch (...) + { + LOG("Couldn't get enabled state"); + } + if (enabled) + { + enable(); + } + else + { + disable(); + } +} + +void VideoConferenceModule::onModuleSettingsChanged() +{ + try + { + PowerToysSettings::PowerToyValues values = PowerToysSettings::PowerToyValues::load_from_settings_file(get_key()); + //Trace::SettingsChanged(pressTime.value, overlayOpacity.value, theme.value); + + if (_enabled) + { + if (const auto val = values.get_json(L"mute_camera_and_microphone_hotkey")) + { + settings.cameraAndMicrophoneMuteHotkey = PowerToysSettings::HotkeyObject::from_json(*val); + } + if (const auto val = values.get_json(L"mute_microphone_hotkey")) + { + settings.microphoneMuteHotkey = PowerToysSettings::HotkeyObject::from_json(*val); + } + if (const auto val = values.get_json(L"mute_camera_hotkey")) + { + settings.cameraMuteHotkey = PowerToysSettings::HotkeyObject::from_json(*val); + } + if (const auto val = values.get_string_value(L"toolbar_position")) + { + settings.toolbarPositionString = val.value(); + } + if (const auto val = values.get_string_value(L"toolbar_monitor")) + { + settings.toolbarMonitorString = val.value(); + } + if (const auto val = values.get_string_value(L"selected_camera"); val && val != settings.selectedCamera) + { + settings.selectedCamera = val.value(); + sendSourceCameraNameUpdate(); + } + if (const auto val = values.get_string_value(L"camera_overlay_image_path"); val && val != settings.imageOverlayPath) + { + settings.imageOverlayPath = val.value(); + sendOverlayImageUpdate(); + } + if (const auto val = values.get_bool_value(L"hide_toolbar_when_unmuted")) + { + toolbar.setHideToolbarWhenUnmuted(val.value()); + } + + const auto selectedMic = values.get_string_value(L"selected_mic"); + if (selectedMic && selectedMic != settings.selectedMicrophone) + { + settings.selectedMicrophone = *selectedMic; + updateControlledMicrophones(settings.selectedMicrophone); + } + + toolbar.show(settings.toolbarPositionString, settings.toolbarMonitorString); + } + } + catch (...) + { + LOG("onModuleSettingsChanged encountered an exception"); + } +} + +VideoConferenceModule::VideoConferenceModule() : + _generalSettingsWatcher{ PTSettingsHelper::get_powertoys_general_save_file_location(), [this] { + toolbar.scheduleGeneralSettingsUpdate(); + } }, + _moduleSettingsWatcher{ PTSettingsHelper::get_module_save_file_location(get_key()), [this] { toolbar.scheduleModuleSettingsUpdate(); } } +{ + init_settings(); + _settingsUpdateChannel = + SerializedSharedMemory::create(CameraSettingsUpdateChannel::endpoint(), sizeof(CameraSettingsUpdateChannel), false); + if (_settingsUpdateChannel) + { + _settingsUpdateChannel->access([](auto memory) { + auto updatesChannel = new (memory._data) CameraSettingsUpdateChannel{}; + }); + } + sendSourceCameraNameUpdate(); + sendOverlayImageUpdate(); +} + +inline VideoConferenceModule::~VideoConferenceModule() +{ + instance->unmuteAll(); + toolbar.hide(); +} + +const wchar_t* VideoConferenceModule::get_name() +{ + return L"Video Conference"; +} + +const wchar_t* VideoConferenceModule::get_key() +{ + return L"Video Conference"; +} + +bool VideoConferenceModule::get_config(wchar_t* buffer, int* buffer_size) +{ + return true; +} + +void VideoConferenceModule::set_config(const wchar_t* config) +{ + try + { + PowerToysSettings::PowerToyValues values = PowerToysSettings::PowerToyValues::from_json_string(config, get_key()); + values.save_to_settings_file(); + } + catch (...) + { + LOG("VideoConferenceModule::set_config: exception during saving new settings values"); + } +} + +void VideoConferenceModule::init_settings() +{ + try + { + PowerToysSettings::PowerToyValues powerToysSettings = PowerToysSettings::PowerToyValues::load_from_settings_file(L"Video Conference"); + + if (const auto val = powerToysSettings.get_json(L"mute_camera_and_microphone_hotkey")) + { + settings.cameraAndMicrophoneMuteHotkey = PowerToysSettings::HotkeyObject::from_json(*val); + } + if (const auto val = powerToysSettings.get_json(L"mute_microphone_hotkey")) + { + settings.microphoneMuteHotkey = PowerToysSettings::HotkeyObject::from_json(*val); + } + if (const auto val = powerToysSettings.get_json(L"mute_camera_hotkey")) + { + settings.cameraMuteHotkey = PowerToysSettings::HotkeyObject::from_json(*val); + } + if (const auto val = powerToysSettings.get_string_value(L"toolbar_position")) + { + settings.toolbarPositionString = val.value(); + } + if (const auto val = powerToysSettings.get_string_value(L"toolbar_monitor")) + { + settings.toolbarMonitorString = val.value(); + } + if (const auto val = powerToysSettings.get_string_value(L"selected_camera")) + { + settings.selectedCamera = val.value(); + } + if (const auto val = powerToysSettings.get_string_value(L"camera_overlay_image_path")) + { + settings.imageOverlayPath = val.value(); + } + if (const auto val = powerToysSettings.get_bool_value(L"hide_toolbar_when_unmuted")) + { + toolbar.setHideToolbarWhenUnmuted(val.value()); + } + if (const auto val = powerToysSettings.get_string_value(L"selected_mic"); val && *val != settings.selectedMicrophone) + { + settings.selectedMicrophone = *val; + updateControlledMicrophones(settings.selectedMicrophone); + } + } + catch (std::exception&) + { + // Error while loading from the settings file. Just let default values stay as they are. + } + + try + { + auto loaded = PTSettingsHelper::load_general_settings(); + std::wstring settings_theme{ static_cast(loaded.GetNamedString(L"theme", L"system")) }; + if (settings_theme != L"dark" && settings_theme != L"light") + { + settings_theme = L"system"; + } + toolbar.setTheme(settings_theme); + } + catch (...) + { + } +} + +void VideoConferenceModule::updateControlledMicrophones(const std::wstring_view new_mic) +{ + for (auto& controlledMic : _controlledMicrophones) + { + controlledMic.set_muted(false); + } + _controlledMicrophones.clear(); + _microphoneTrackedInUI = nullptr; + auto allMics = MicrophoneDevice::getAllActive(); + if (new_mic == L"[All]") + { + _controlledMicrophones = std::move(allMics); + if (auto defaultMic = MicrophoneDevice::getDefault()) + { + for (auto& controlledMic : _controlledMicrophones) + { + if (controlledMic.id() == defaultMic->id()) + { + _microphoneTrackedInUI = &controlledMic; + break; + } + } + } + } + else + { + for (auto& controlledMic : allMics) + { + if (controlledMic.name() == new_mic) + { + _controlledMicrophones.emplace_back(std::move(controlledMic)); + _microphoneTrackedInUI = &_controlledMicrophones[0]; + break; + } + } + } + if (_microphoneTrackedInUI) + { + _microphoneTrackedInUI->set_mute_changed_callback([&](const bool muted) { + toolbar.setMicrophoneMute(muted); + }); + toolbar.setMicrophoneMute(_microphoneTrackedInUI->muted()); + } +} + +void toggleProxyCamRegistration(const bool enable) +{ + if (!is_process_elevated()) + { + return; + } + + auto vcmRoot = fs::path{ get_module_folderpath() } / "modules"; + vcmRoot /= "VideoConference"; + + std::array proxyFilters = { vcmRoot / "VideoConferenceProxyFilter_x64.dll", vcmRoot / "VideoConferenceProxyFilter_x86.dll" }; + for (const auto filter : proxyFilters) + { + std::wstring params{ L"/s " }; + if (!enable) + { + params += L"/u "; + } + params += '"'; + params += filter; + params += '"'; + SHELLEXECUTEINFOW sei{ sizeof(sei) }; + sei.fMask = { SEE_MASK_FLAG_NO_UI | SEE_MASK_NOASYNC }; + sei.lpFile = L"regsvr32"; + sei.lpParameters = params.c_str(); + sei.nShow = SW_SHOWNORMAL; + ShellExecuteExW(&sei); + } +} + +void VideoConferenceModule::enable() +{ + if (!_enabled) + { + toggleProxyCamRegistration(true); + toolbar.setMicrophoneMute(getMicrophoneMuteState()); + toolbar.setCameraMute(getVirtualCameraMuteState()); + + toolbar.show(settings.toolbarPositionString, settings.toolbarMonitorString); + + _enabled = true; + +#if defined(DISABLE_LOWLEVEL_HOOKS_WHEN_DEBUGGED) + if (IsDebuggerPresent()) + { + return; + } +#endif + hook_handle = SetWindowsHookEx(WH_KEYBOARD_LL, LowLevelKeyboardProc, GetModuleHandle(NULL), NULL); + } +} + +void VideoConferenceModule::unmuteAll() +{ + if (getVirtualCameraMuteState()) + { + reverseVirtualCameraMuteState(); + } + + if (getMicrophoneMuteState()) + { + reverseMicrophoneMute(); + } +} + +void VideoConferenceModule::disable() +{ + if (_enabled) + { + toggleProxyCamRegistration(false); + if (hook_handle) + { + bool success = UnhookWindowsHookEx(hook_handle); + if (success) + { + hook_handle = nullptr; + } + } + + instance->unmuteAll(); + toolbar.hide(); + + _enabled = false; + } +} + +bool VideoConferenceModule::is_enabled() +{ + return _enabled; +} + +void VideoConferenceModule::destroy() +{ + delete this; + instance = nullptr; +} + +void VideoConferenceModule::sendSourceCameraNameUpdate() +{ + if (!_settingsUpdateChannel.has_value() || settings.selectedCamera.empty()) + { + return; + } + _settingsUpdateChannel->access([](auto memory) { + auto updatesChannel = reinterpret_cast(memory._data); + updatesChannel->sourceCameraName.emplace(); + std::copy(begin(settings.selectedCamera), end(settings.selectedCamera), begin(*updatesChannel->sourceCameraName)); + }); +} + +void VideoConferenceModule::sendOverlayImageUpdate() +{ + if (!_settingsUpdateChannel.has_value()) + { + return; + } + _imageOverlayChannel.reset(); + + wchar_t powertoysDirectory[MAX_PATH + 1]; + + DWORD length = GetModuleFileNameW(nullptr, powertoysDirectory, MAX_PATH); + PathRemoveFileSpecW(powertoysDirectory); + + std::wstring blankImagePath(powertoysDirectory); + blankImagePath += L"\\modules\\VideoConference\\black.bmp"; + + _imageOverlayChannel = SerializedSharedMemory::create_readonly(CameraOverlayImageChannel::endpoint(), + settings.imageOverlayPath != L"" ? settings.imageOverlayPath : blankImagePath); + + const auto imageSize = static_cast(_imageOverlayChannel->size()); + _settingsUpdateChannel->access([imageSize](auto memory) { + auto updatesChannel = reinterpret_cast(memory._data); + updatesChannel->overlayImageSize.emplace(imageSize); + updatesChannel->newOverlayImagePosted = true; + }); +} diff --git a/src/modules/videoconference/VideoConferenceModule/VideoConferenceModule.h b/src/modules/videoconference/VideoConferenceModule/VideoConferenceModule.h new file mode 100644 index 0000000000..2a37546362 --- /dev/null +++ b/src/modules/videoconference/VideoConferenceModule/VideoConferenceModule.h @@ -0,0 +1,86 @@ +#pragma once + +#include + +#include +#include + +#include + +#include +#include + +#include "Toolbar.h" + +#include + +extern class VideoConferenceModule* instance; + +struct VideoConferenceSettings +{ + PowerToysSettings::HotkeyObject cameraAndMicrophoneMuteHotkey = PowerToysSettings::HotkeyObject::from_settings(true, false, false, false, 78); + PowerToysSettings::HotkeyObject microphoneMuteHotkey = PowerToysSettings::HotkeyObject::from_settings(true, false, false, true, 65); + PowerToysSettings::HotkeyObject cameraMuteHotkey = PowerToysSettings::HotkeyObject::from_settings(true, false, false, true, 79); + + std::wstring toolbarPositionString; + std::wstring toolbarMonitorString; + + std::wstring selectedCamera; + std::wstring imageOverlayPath; + std::wstring selectedMicrophone; +}; + +class VideoConferenceModule : public PowertoyModuleIface +{ +public: + VideoConferenceModule(); + ~VideoConferenceModule(); + virtual const wchar_t* get_name() override; + + virtual bool get_config(wchar_t* buffer, int* buffer_size) override; + + virtual void set_config(const wchar_t* config) override; + + virtual void enable() override; + virtual void disable() override; + virtual bool is_enabled() override; + virtual void destroy() override; + + virtual const wchar_t * get_key() override; + + void sendSourceCameraNameUpdate(); + void sendOverlayImageUpdate(); + + static void unmuteAll(); + static void reverseMicrophoneMute(); + static bool getMicrophoneMuteState(); + static void reverseVirtualCameraMuteState(); + static bool getVirtualCameraMuteState(); + static bool getVirtualCameraInUse(); + + void onGeneralSettingsChanged(); + void onModuleSettingsChanged(); +private: + + void init_settings(); + void updateControlledMicrophones(const std::wstring_view new_mic); + // all callback methods and used by callback have to be static + static LRESULT CALLBACK LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam); + static bool isKeyPressed(unsigned int keyCode); + static bool isHotkeyPressed(DWORD code, PowerToysSettings::HotkeyObject& hotkey); + + static HHOOK hook_handle; + bool _enabled = false; + + std::vector _controlledMicrophones; + MicrophoneDevice* _microphoneTrackedInUI = nullptr; + + std::optional _imageOverlayChannel; + std::optional _settingsUpdateChannel; + + FileWatcher _generalSettingsWatcher; + FileWatcher _moduleSettingsWatcher; + + static VideoConferenceSettings settings; + static Toolbar toolbar; +}; diff --git a/src/modules/videoconference/VideoConferenceModule/black.bmp b/src/modules/videoconference/VideoConferenceModule/black.bmp new file mode 100644 index 0000000000..18d40779ce Binary files /dev/null and b/src/modules/videoconference/VideoConferenceModule/black.bmp differ diff --git a/src/modules/videoconference/VideoConferenceModule/dllmain.cpp b/src/modules/videoconference/VideoConferenceModule/dllmain.cpp new file mode 100644 index 0000000000..c3d012cd6c --- /dev/null +++ b/src/modules/videoconference/VideoConferenceModule/dllmain.cpp @@ -0,0 +1,35 @@ +// dllmain.cpp : Defines the entry point for the DLL application. +#include "pch.h" +#include +#include "trace.h" +#include "VideoConferenceModule.h" + +BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) +{ + switch (ul_reason_for_call) + { + case DLL_PROCESS_ATTACH: + Trace::RegisterProvider(); + break; + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + break; + case DLL_PROCESS_DETACH: + Trace::UnregisterProvider(); + break; + } + return TRUE; +} + +extern "C" __declspec(dllexport) PowertoyModuleIface* __cdecl powertoy_create() +{ + if (!instance) + { + instance = new VideoConferenceModule(); + return instance; + } + else + { + return nullptr; + } +} diff --git a/src/modules/videoconference/VideoConferenceModule/framework.h b/src/modules/videoconference/VideoConferenceModule/framework.h new file mode 100644 index 0000000000..54b83e94fd --- /dev/null +++ b/src/modules/videoconference/VideoConferenceModule/framework.h @@ -0,0 +1,5 @@ +#pragma once + +#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers +// Windows Header Files +#include diff --git a/src/modules/videoconference/VideoConferenceModule/packages.config b/src/modules/videoconference/VideoConferenceModule/packages.config new file mode 100644 index 0000000000..20da4fefa3 --- /dev/null +++ b/src/modules/videoconference/VideoConferenceModule/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/modules/videoconference/VideoConferenceModule/pch.cpp b/src/modules/videoconference/VideoConferenceModule/pch.cpp new file mode 100644 index 0000000000..64b7eef6d6 --- /dev/null +++ b/src/modules/videoconference/VideoConferenceModule/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/src/modules/videoconference/VideoConferenceModule/pch.h b/src/modules/videoconference/VideoConferenceModule/pch.h new file mode 100644 index 0000000000..8e268511bc --- /dev/null +++ b/src/modules/videoconference/VideoConferenceModule/pch.h @@ -0,0 +1,24 @@ +#pragma once +#define WIN32_LEAN_AND_MEAN +#include +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include diff --git a/src/modules/videoconference/VideoConferenceModule/trace.cpp b/src/modules/videoconference/VideoConferenceModule/trace.cpp new file mode 100644 index 0000000000..377d49b18a --- /dev/null +++ b/src/modules/videoconference/VideoConferenceModule/trace.cpp @@ -0,0 +1,57 @@ +#include "pch.h" + +#include "trace.h" + +TRACELOGGING_DEFINE_PROVIDER( + g_hProvider, + "Microsoft.PowerToys", + // {38e8889b-9731-53f5-e901-e8a7c1753074} + (0x38e8889b, 0x9731, 0x53f5, 0xe9, 0x01, 0xe8, 0xa7, 0xc1, 0x75, 0x30, 0x74), + TraceLoggingOptionProjectTelemetry()); + +void Trace::RegisterProvider() noexcept +{ + TraceLoggingRegister(g_hProvider); +} + +void Trace::UnregisterProvider() noexcept +{ + TraceLoggingUnregister(g_hProvider); +} + +void Trace::SettingsChanged(const struct VideoConferenceSettings& settings) noexcept +{ + bool CustomOverlayImage = (settings.imageOverlayPath.length() > 0); + + TraceLoggingWrite( + g_hProvider, + "VideoConference_SettingsChanged", + TraceLoggingWideString(settings.toolbarPositionString.c_str(), "ToolbarPosition"), + TraceLoggingWideString(settings.toolbarMonitorString.c_str(), "ToolbarMonitorSelection"), + TraceLoggingBool(CustomOverlayImage, "CustomImageOverlayUsed"), + ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance), + TraceLoggingBoolean(TRUE, "UTCReplace_AppSessionGuid"), + TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE)); +} + +void Trace::MicrophoneMuted() noexcept +{ + TraceLoggingWrite( + g_hProvider, + "VideoConference_MicrophoneMuted", + TraceLoggingBoolean(true, "MicrophoneMuted"), + ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance), + TraceLoggingBoolean(TRUE, "UTCReplace_AppSessionGuid"), + TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE)); +} + +void Trace::CameraMuted() noexcept +{ + TraceLoggingWrite( + g_hProvider, + "VideoConference_CameraMuted", + TraceLoggingBoolean(true, "CameraMuted"), + ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance), + TraceLoggingBoolean(TRUE, "UTCReplace_AppSessionGuid"), + TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE)); +} diff --git a/src/modules/videoconference/VideoConferenceModule/trace.h b/src/modules/videoconference/VideoConferenceModule/trace.h new file mode 100644 index 0000000000..c9283256df --- /dev/null +++ b/src/modules/videoconference/VideoConferenceModule/trace.h @@ -0,0 +1,12 @@ +#pragma once +#include "VideoConferenceModule.h" + +class Trace +{ +public: + static void RegisterProvider() noexcept; + static void UnregisterProvider() noexcept; + static void SettingsChanged(const struct VideoConferenceSettings &settings) noexcept; + static void MicrophoneMuted() noexcept; + static void CameraMuted() noexcept; +}; diff --git a/src/modules/videoconference/VideoConferenceProxyFilter/DirectShowUtils.cpp b/src/modules/videoconference/VideoConferenceProxyFilter/DirectShowUtils.cpp new file mode 100644 index 0000000000..00359670f3 --- /dev/null +++ b/src/modules/videoconference/VideoConferenceProxyFilter/DirectShowUtils.cpp @@ -0,0 +1,118 @@ +#include "DirectShowUtils.h" + +#include + +unique_media_type_ptr CopyMediaType(const AM_MEDIA_TYPE* source) +{ + unique_media_type_ptr target{ static_cast(CoTaskMemAlloc(sizeof(AM_MEDIA_TYPE))) }; + *target = *source; + if (source->cbFormat) + { + target->pbFormat = static_cast(CoTaskMemAlloc(source->cbFormat)); + std::copy(source->pbFormat, source->pbFormat + source->cbFormat, target->pbFormat); + } + + if (target->pUnk) + { + target->pUnk->AddRef(); + } + + return target; +} + +wil::com_ptr_nothrow GetPinAllocator(wil::com_ptr_nothrow& inputPin) +{ + if (!inputPin) + { + return nullptr; + } + wil::com_ptr_nothrow allocator; + if (auto memInput = inputPin.try_query(); memInput) + { + memInput->GetAllocator(&allocator); + return allocator; + } + + return nullptr; +} + +unique_media_type_ptr CopyMediaType(const unique_media_type_ptr& source) +{ + return CopyMediaType(source.get()); +} + +void MyFreeMediaType(AM_MEDIA_TYPE& mt) +{ + if (mt.cbFormat != 0) + { + CoTaskMemFree(mt.pbFormat); + mt.cbFormat = 0; + mt.pbFormat = nullptr; + } + + if (mt.pUnk != nullptr) + { + mt.pUnk->Release(); + mt.pUnk = nullptr; + } +} + +void MyDeleteMediaType(AM_MEDIA_TYPE* pmt) +{ + if (!pmt) + { + return; + } + + MyFreeMediaType(*pmt); + CoTaskMemFree(const_cast(pmt)); +} + +HRESULT MediaTypeEnumerator::Next(ULONG cObjects, AM_MEDIA_TYPE** outObjects, ULONG* pcFetched) +{ + if (!outObjects) + { + return E_POINTER; + } + + ULONG fetched = 0; + ULONG toFetch = cObjects; + while (toFetch-- && _pos < _objects.size()) + { + auto copy = CopyMediaType(_objects[_pos++]); + outObjects[fetched++] = copy.release(); + } + + if (pcFetched) + { + *pcFetched = fetched; + } + + return fetched == cObjects ? S_OK : S_FALSE; +} + +HRESULT MediaTypeEnumerator::Skip(ULONG cObjects) +{ + _pos += cObjects; + return _pos < _objects.size() ? S_OK : S_FALSE; +} + +HRESULT MediaTypeEnumerator::Reset() +{ + _pos = 0; + return S_OK; +} + +HRESULT MediaTypeEnumerator::Clone(IEnumMediaTypes** ppEnum) +{ + auto cloned = winrt::make_self(); + cloned->_objects.resize(_objects.size()); + for (size_t i = 0; i < _objects.size(); ++i) + { + cloned->_objects[i] = CopyMediaType(_objects[i]); + } + + cloned->_pos = _pos; + cloned.as().copy_to(ppEnum); + return S_OK; +} \ No newline at end of file diff --git a/src/modules/videoconference/VideoConferenceProxyFilter/DirectShowUtils.h b/src/modules/videoconference/VideoConferenceProxyFilter/DirectShowUtils.h new file mode 100644 index 0000000000..4c5534d3e6 --- /dev/null +++ b/src/modules/videoconference/VideoConferenceProxyFilter/DirectShowUtils.h @@ -0,0 +1,88 @@ +#pragma once +#include + +#define WIN32_LEAN_AND_MEAN +#include +#include + +#include +#include + +#include + +#include "Logging.h" + +void MyDeleteMediaType(AM_MEDIA_TYPE* pmt); + +using unique_media_type_ptr = + wistd::unique_ptr>; + +unique_media_type_ptr CopyMediaType(const unique_media_type_ptr& source); +unique_media_type_ptr CopyMediaType(const AM_MEDIA_TYPE* source); + +template +struct ObjectEnumerator : public winrt::implements, EnumeratorInterface> +{ + std::vector> _objects; + ULONG _pos = 0; + + HRESULT STDMETHODCALLTYPE Next(ULONG cObjects, ObjectInterface** outObjects, ULONG* pcFetched) override + { + if (!outObjects) + { + return E_POINTER; + } + + ULONG fetched = 0; + ULONG toFetch = cObjects; + while (toFetch-- && _pos < _objects.size()) + { + _objects[_pos++].copy_to(&outObjects[fetched++]); + } + + if (pcFetched) + { + *pcFetched = fetched; + } + + return fetched == cObjects ? S_OK : S_FALSE; + } + + HRESULT STDMETHODCALLTYPE Skip(ULONG cObjects) override + { + _pos += cObjects; + return _pos < _objects.size() ? S_OK : S_FALSE; + } + + HRESULT STDMETHODCALLTYPE Reset() override + { + _pos = 0; + return S_OK; + } + + HRESULT STDMETHODCALLTYPE Clone(EnumeratorInterface** ppEnum) override + { + auto cloned = winrt::make_self(); + cloned->_objects = _objects; + cloned->_pos = _pos; + cloned.as().copy_to(ppEnum); + return S_OK; + } + + virtual ~ObjectEnumerator() = default; +}; + +struct MediaTypeEnumerator : public winrt::implements +{ + std::vector _objects; + ULONG _pos = 0; + + HRESULT STDMETHODCALLTYPE Next(ULONG cObjects, AM_MEDIA_TYPE** outObjects, ULONG* pcFetched) override; + HRESULT STDMETHODCALLTYPE Skip(ULONG cObjects) override; + HRESULT STDMETHODCALLTYPE Reset() override; + HRESULT STDMETHODCALLTYPE Clone(IEnumMediaTypes** ppEnum) override; + + virtual ~MediaTypeEnumerator() = default; +}; + +wil::com_ptr_nothrow GetPinAllocator(wil::com_ptr_nothrow& inputPin); diff --git a/src/modules/videoconference/VideoConferenceProxyFilter/ImageLoading.cpp b/src/modules/videoconference/VideoConferenceProxyFilter/ImageLoading.cpp new file mode 100644 index 0000000000..656fd5ea7f --- /dev/null +++ b/src/modules/videoconference/VideoConferenceProxyFilter/ImageLoading.cpp @@ -0,0 +1,425 @@ +#include + +#include +#include +#include + +#pragma warning(push) +#pragma warning(disable : 4005) +#include +#pragma warning(pop) + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include "Logging.h" + +IWICImagingFactory* _GetWIC() noexcept +{ + static IWICImagingFactory* s_Factory = nullptr; + + if (s_Factory) + { + return s_Factory; + } + + OK_OR_BAIL(CoCreateInstance( + CLSID_WICImagingFactory, nullptr, CLSCTX_INPROC_SERVER, __uuidof(IWICImagingFactory), (LPVOID*)&s_Factory)); + + return s_Factory; +} + +bool ReencodeJPGImage(BYTE* imageBuf, const DWORD imageSize, DWORD& reencodedSize) +{ + auto pWIC = _GetWIC(); + wil::com_ptr_nothrow imageStream = SHCreateMemStream(imageBuf, imageSize); + if (!imageStream) + { + return false; + } + + // Decode jpg into bitmap + wil::com_ptr_nothrow bitmapDecoder; + OK_OR_BAIL(pWIC->CreateDecoderFromStream(imageStream.get(), nullptr, WICDecodeMetadataCacheOnLoad, &bitmapDecoder)); + wil::com_ptr_nothrow decodedFrame; + OK_OR_BAIL(bitmapDecoder->GetFrame(0, &decodedFrame)); + wil::com_ptr_nothrow bitmap; + bitmap.attach(decodedFrame.detach()); + UINT width = 0, height = 0; + OK_OR_BAIL(bitmap->GetSize(&width, &height)); + + // Initialize jpg encoder + wil::com_ptr_nothrow encoder; + OK_OR_BAIL(pWIC->CreateEncoder(GUID_ContainerFormatJpeg, nullptr, &encoder)); + + wil::com_ptr_nothrow outputStream; + OK_OR_BAIL(CreateStreamOnHGlobal(nullptr, true, &outputStream)); + OK_OR_BAIL(encoder->Initialize(outputStream.get(), WICBitmapEncoderNoCache)); + wil::com_ptr_nothrow encodedFrame; + wil::com_ptr_nothrow encoderOptions; + OK_OR_BAIL(encoder->CreateNewFrame(&encodedFrame, &encoderOptions)); + + ULONG nProperties = 0; + OK_OR_BAIL(encoderOptions->CountProperties(&nProperties)); + for (ULONG propIdx = 0; propIdx < nProperties; ++propIdx) + { + PROPBAG2 propBag{}; + ULONG _; + OK_OR_BAIL(encoderOptions->GetPropertyInfo(propIdx, 1, &propBag, &_)); + if (propBag.pstrName == std::wstring_view{ L"ImageQuality" }) + { + wil::unique_variant variant; + variant.vt = VT_R4; + variant.fltVal = 0.1f; + OK_OR_BAIL(encoderOptions->Write(1, &propBag, &variant)); + LOG("Successfully set jpg compression quality"); + // skip the rest of the properties + propIdx = nProperties; + } + CoTaskMemFree(propBag.pstrName); + } + + OK_OR_BAIL(encodedFrame->Initialize(encoderOptions.get())); + WICPixelFormatGUID intermediateFormat = GUID_WICPixelFormat24bppRGB; + + OK_OR_BAIL(encodedFrame->SetPixelFormat(&intermediateFormat)); + OK_OR_BAIL(encodedFrame->SetSize(width, height)); + + // Commit the image encoding + OK_OR_BAIL(encodedFrame->WriteSource(bitmap.get(), nullptr)); + OK_OR_BAIL(encodedFrame->Commit()); + OK_OR_BAIL(encoder->Commit()); + + STATSTG intermediateStreamStat{}; + OK_OR_BAIL(outputStream->Stat(&intermediateStreamStat, STATFLAG_NONAME)); + const ULONGLONG jpgStreamSize = intermediateStreamStat.cbSize.QuadPart; + HGLOBAL streamMemoryHandle{}; + OK_OR_BAIL(GetHGlobalFromStream(outputStream.get(), &streamMemoryHandle)); + + auto jpgStreamMemory = static_cast(GlobalLock(streamMemoryHandle)); + std::copy(jpgStreamMemory, jpgStreamMemory + jpgStreamSize, imageBuf); + auto unlockJpgStreamMemory = wil::scope_exit([jpgStreamMemory] { GlobalUnlock(jpgStreamMemory); }); + reencodedSize = (DWORD)jpgStreamSize; + return true; +} + +wil::com_ptr_nothrow LoadAsRGB24BitmapWithSize(IWICImagingFactory* pWIC, + wil::com_ptr_nothrow image, + const UINT targetWidth, + const UINT targetHeight) +{ + wil::com_ptr_nothrow bitmap; + // Initialize image bitmap decoder from filename and get the image frame + wil::com_ptr_nothrow bitmapDecoder; + OK_OR_BAIL(pWIC->CreateDecoderFromStream(image.get(), nullptr, WICDecodeMetadataCacheOnLoad, &bitmapDecoder)); + + wil::com_ptr_nothrow decodedFrame; + OK_OR_BAIL(bitmapDecoder->GetFrame(0, &decodedFrame)); + + UINT imageWidth = 0, imageHeight = 0; + OK_OR_BAIL(decodedFrame->GetSize(&imageWidth, &imageHeight)); + + // Scale the image if required + if (targetWidth != imageWidth || targetHeight != imageHeight) + { + wil::com_ptr_nothrow scaler; + OK_OR_BAIL(pWIC->CreateBitmapScaler(&scaler)); + OK_OR_BAIL( + scaler->Initialize(decodedFrame.get(), targetWidth, targetHeight, WICBitmapInterpolationModeHighQualityCubic)); + bitmap.attach(scaler.detach()); + } + else + { + bitmap.attach(decodedFrame.detach()); + } + WICPixelFormatGUID pixelFormat{}; + OK_OR_BAIL(bitmap->GetPixelFormat(&pixelFormat)); + + const auto targetPixelFormat = GUID_WICPixelFormat24bppBGR; + if (pixelFormat != targetPixelFormat) + { + wil::com_ptr_nothrow convertedBitmap; + if (SUCCEEDED(WICConvertBitmapSource(targetPixelFormat, bitmap.get(), &convertedBitmap))) + { + return convertedBitmap; + } + } + + return bitmap; +} + +wil::com_ptr_nothrow EncodeBitmapToContainer(IWICImagingFactory* pWIC, + wil::com_ptr_nothrow bitmap, + const GUID& containerGUID, + const UINT width, + const UINT height, + const float quality) +{ + wil::com_ptr_nothrow encoder; + pWIC->CreateEncoder(containerGUID, nullptr, &encoder); + + if (!encoder) + { + return nullptr; + } + + // Prepare the encoder output memory stream and encoding params + wil::com_ptr_nothrow encodedBitmap; + OK_OR_BAIL(CreateStreamOnHGlobal(nullptr, true, &encodedBitmap)); + OK_OR_BAIL(encoder->Initialize(encodedBitmap.get(), WICBitmapEncoderNoCache)); + wil::com_ptr_nothrow encodedFrame; + + wil::com_ptr_nothrow encoderOptions; + OK_OR_BAIL(encoder->CreateNewFrame(&encodedFrame, &encoderOptions)); + + ULONG nProperties = 0; + OK_OR_BAIL(encoderOptions->CountProperties(&nProperties)); + for (ULONG propIdx = 0; propIdx < nProperties; ++propIdx) + { + PROPBAG2 propBag{}; + ULONG _; + OK_OR_BAIL(encoderOptions->GetPropertyInfo(propIdx, 1, &propBag, &_)); + if (propBag.pstrName == std::wstring_view{ L"ImageQuality" }) + { + wil::unique_variant variant; + variant.vt = VT_R4; + variant.fltVal = quality; + OK_OR_BAIL(encoderOptions->Write(1, &propBag, &variant)); + LOG("Successfully set jpg compression quality"); + // skip the rest of the properties + propIdx = nProperties; + } + CoTaskMemFree(propBag.pstrName); + } + + OK_OR_BAIL(encodedFrame->Initialize(encoderOptions.get())); + + WICPixelFormatGUID intermediateFormat = GUID_WICPixelFormat24bppRGB; + OK_OR_BAIL(encodedFrame->SetPixelFormat(&intermediateFormat)); + OK_OR_BAIL(encodedFrame->SetSize(width, height)); + + // Commit the image encoding + OK_OR_BAIL(encodedFrame->WriteSource(bitmap.get(), nullptr)); + OK_OR_BAIL(encodedFrame->Commit()); + OK_OR_BAIL(encoder->Commit()); + return encodedBitmap; +} + +IMFSample* ConvertIMFVideoSample(const MFT_REGISTER_TYPE_INFO& inputType, + IMFMediaType* outputMediaType, + const wil::com_ptr_nothrow& inputSample, + const UINT width, + const UINT height) +{ + IMFActivate** ppVDActivate = nullptr; + UINT32 count = 0; + + MFT_REGISTER_TYPE_INFO outputType = { MFMediaType_Video, {} }; + outputMediaType->GetGUID(MF_MT_SUBTYPE, &outputType.guidSubtype); + + const std::array transformerCategories = { + MFT_CATEGORY_VIDEO_PROCESSOR, MFT_CATEGORY_VIDEO_DECODER, MFT_CATEGORY_VIDEO_ENCODER + }; + + for (const auto& transformerCategory : transformerCategories) + { + OK_OR_BAIL(MFTEnumEx(transformerCategory, MFT_ENUM_FLAG_SYNCMFT, &inputType, &outputType, &ppVDActivate, &count)); + if (count != 0) + { + break; + } + } + + wil::com_ptr_nothrow videoTransformer; + + bool videoDecoderActivated = false; + for (UINT32 i = 0; i < count; ++i) + { + if (!videoDecoderActivated && !FAILED(ppVDActivate[i]->ActivateObject(IID_PPV_ARGS(&videoTransformer)))) + { + videoDecoderActivated = true; + } + ppVDActivate[i]->Release(); + } + + if (count) + { + CoTaskMemFree(ppVDActivate); + } + + if (!videoDecoderActivated) + { + LOG("No converter avialable for the selected format"); + return nullptr; + } + + auto shutdownVideoDecoder = wil::scope_exit([&videoTransformer] { MFShutdownObject(videoTransformer.get()); }); + // Set input/output types for the decoder + wil::com_ptr_nothrow intermediateFrameMediaType; + OK_OR_BAIL(MFCreateMediaType(&intermediateFrameMediaType)); + intermediateFrameMediaType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video); + intermediateFrameMediaType->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_RGB24); + intermediateFrameMediaType->SetUINT32(MF_MT_INTERLACE_MODE, MFVideoInterlace_Progressive); + intermediateFrameMediaType->SetUINT32(MF_MT_ALL_SAMPLES_INDEPENDENT, TRUE); + OK_OR_BAIL(MFSetAttributeSize(intermediateFrameMediaType.get(), MF_MT_FRAME_SIZE, width, height)); + OK_OR_BAIL(MFSetAttributeRatio(intermediateFrameMediaType.get(), MF_MT_PIXEL_ASPECT_RATIO, width, height)); + OK_OR_BAIL(videoTransformer->SetInputType(0, intermediateFrameMediaType.get(), 0)); + OK_OR_BAIL(videoTransformer->SetOutputType(0, outputMediaType, 0)); + + // Process the input sample + OK_OR_BAIL(videoTransformer->ProcessInput(0, inputSample.get(), 0)); + + // Check whether we need to allocate output sample and buffer ourselves + MFT_OUTPUT_STREAM_INFO outputStreamInfo{}; + OK_OR_BAIL(videoTransformer->GetOutputStreamInfo(0, &outputStreamInfo)); + const bool onlyProvidesSamples = outputStreamInfo.dwFlags & MFT_OUTPUT_STREAM_PROVIDES_SAMPLES; + const bool canProvideSamples = outputStreamInfo.dwFlags & MFT_OUTPUT_STREAM_CAN_PROVIDE_SAMPLES; + const bool mustAllocateSample = + (!onlyProvidesSamples && !canProvideSamples) || + (!onlyProvidesSamples && (outputStreamInfo.dwFlags & MFT_PROCESS_OUTPUT_DISCARD_WHEN_NO_BUFFER)); + + MFT_OUTPUT_DATA_BUFFER outputSamples{}; + IMFSample* outputSample = nullptr; + + // If so, do the allocation + if (mustAllocateSample) + { + OK_OR_BAIL(MFCreateSample(&outputSample)); + OK_OR_BAIL(outputSample->SetSampleDuration(333333)); + OK_OR_BAIL(outputSample->SetSampleTime(1)); + OK_OR_BAIL(outputSample->SetUINT32(MF_MT_VIDEO_ROTATION, MFVideoRotationFormat::MFVideoRotationFormat_0)); + IMFMediaBuffer* outputMediaBuffer = nullptr; + OK_OR_BAIL( + MFCreateAlignedMemoryBuffer(outputStreamInfo.cbSize, outputStreamInfo.cbAlignment - 1, &outputMediaBuffer)); + OK_OR_BAIL(outputMediaBuffer->SetCurrentLength(outputStreamInfo.cbSize)); + OK_OR_BAIL(outputSample->AddBuffer(outputMediaBuffer)); + outputSamples.pSample = outputSample; + } + + // Finally, produce the output sample + DWORD processStatus = 0; + if (failed(videoTransformer->ProcessOutput(0, 1, &outputSamples, &processStatus))) + { + LOG("Failed to convert image frame"); + } + if (outputSamples.pEvents) + { + outputSamples.pEvents->Release(); + } + + return outputSamples.pSample; +} + +wil::com_ptr_nothrow LoadImageAsSample(wil::com_ptr_nothrow imageStream, + IMFMediaType* sampleMediaType, + const float quality) noexcept +{ + UINT targetWidth = 0; + UINT targetHeight = 0; + OK_OR_BAIL(MFGetAttributeSize(sampleMediaType, MF_MT_FRAME_SIZE, &targetWidth, &targetHeight)); + MFT_REGISTER_TYPE_INFO outputType = { MFMediaType_Video, {} }; + OK_OR_BAIL(sampleMediaType->GetGUID(MF_MT_SUBTYPE, &outputType.guidSubtype)); + + IWICImagingFactory* pWIC = _GetWIC(); + if (!pWIC) + { + LOG("Failed to create IWICImagingFactory"); + return nullptr; + } + + if (!imageStream) + { + return nullptr; + } + + const auto srcImageBitmap = LoadAsRGB24BitmapWithSize(pWIC, imageStream, targetWidth, targetHeight); + if (!srcImageBitmap) + { + return nullptr; + } + + // First, let's create a sample containing RGB24 bitmap + IMFSample* outputSample = nullptr; + OK_OR_BAIL(MFCreateSample(&outputSample)); + OK_OR_BAIL(outputSample->SetUINT32(MF_MT_VIDEO_ROTATION, MFVideoRotationFormat::MFVideoRotationFormat_0)); + OK_OR_BAIL(outputSample->SetSampleDuration(333333)); + OK_OR_BAIL(outputSample->SetSampleTime(1)); + IMFMediaBuffer* outputMediaBuffer = nullptr; + const DWORD nPixelBytes = targetWidth * targetHeight * 3; + OK_OR_BAIL(MFCreateAlignedMemoryBuffer(nPixelBytes, MF_64_BYTE_ALIGNMENT, &outputMediaBuffer)); + + const UINT stride = 3 * targetWidth; + + DWORD max_length = 0, current_length = 0; + BYTE* sampleBufferMemory = nullptr; + OK_OR_BAIL(outputMediaBuffer->Lock(&sampleBufferMemory, &max_length, ¤t_length)); + OK_OR_BAIL(srcImageBitmap->CopyPixels(nullptr, stride, nPixelBytes, sampleBufferMemory)); + OK_OR_BAIL(outputMediaBuffer->Unlock()); + + OK_OR_BAIL(outputMediaBuffer->SetCurrentLength(nPixelBytes)); + OK_OR_BAIL(outputSample->AddBuffer(outputMediaBuffer)); + + if (outputType.guidSubtype == MFVideoFormat_RGB24) + { + return outputSample; + } + + // Special case for mjpg, since we need to use jpg container for it instead of supplying raw pixels + if (outputType.guidSubtype == MFVideoFormat_MJPG) + { + // Use an intermediate jpg container sample which will be transcoded to the target format + wil::com_ptr_nothrow jpgStream = + EncodeBitmapToContainer(pWIC, srcImageBitmap, GUID_ContainerFormatJpeg, targetWidth, targetHeight, quality); + + // Obtain stream size and lock its memory pointer + STATSTG intermediateStreamStat{}; + OK_OR_BAIL(jpgStream->Stat(&intermediateStreamStat, STATFLAG_NONAME)); + const ULONGLONG jpgStreamSize = intermediateStreamStat.cbSize.QuadPart; + HGLOBAL streamMemoryHandle{}; + OK_OR_BAIL(GetHGlobalFromStream(jpgStream.get(), &streamMemoryHandle)); + + auto jpgStreamMemory = static_cast(GlobalLock(streamMemoryHandle)); + auto unlockJpgStreamMemory = wil::scope_exit([jpgStreamMemory] { GlobalUnlock(jpgStreamMemory); }); + + // Create a sample from the input image buffer + wil::com_ptr_nothrow jpgSample; + OK_OR_BAIL(MFCreateSample(&jpgSample)); + OK_OR_BAIL(jpgSample->SetUINT32(MF_MT_VIDEO_ROTATION, MFVideoRotationFormat::MFVideoRotationFormat_0)); + IMFMediaBuffer* inputMediaBuffer = nullptr; + OK_OR_BAIL(MFCreateAlignedMemoryBuffer(static_cast(jpgStreamSize), MF_64_BYTE_ALIGNMENT, &inputMediaBuffer)); + BYTE* inputBuf = nullptr; + OK_OR_BAIL(inputMediaBuffer->Lock(&inputBuf, &max_length, ¤t_length)); + if (max_length < jpgStreamSize) + { + return nullptr; + } + + std::copy(jpgStreamMemory, jpgStreamMemory + jpgStreamSize, inputBuf); + unlockJpgStreamMemory.reset(); + OK_OR_BAIL(inputMediaBuffer->Unlock()); + OK_OR_BAIL(inputMediaBuffer->SetCurrentLength(static_cast(jpgStreamSize))); + OK_OR_BAIL(jpgSample->AddBuffer(inputMediaBuffer)); + + return jpgSample; + } + + // Now we are ready to convert it to the requested media type + MFT_REGISTER_TYPE_INFO intermediateType = { MFMediaType_Video, MFVideoFormat_RGB24 }; + + // But if no conversion is needed, just return the input sample + + return ConvertIMFVideoSample(intermediateType, sampleMediaType, outputSample, targetWidth, targetHeight); +} \ No newline at end of file diff --git a/src/modules/videoconference/VideoConferenceProxyFilter/VideoCaptureDevice.cpp b/src/modules/videoconference/VideoConferenceProxyFilter/VideoCaptureDevice.cpp new file mode 100644 index 0000000000..ec40ea66c4 --- /dev/null +++ b/src/modules/videoconference/VideoConferenceProxyFilter/VideoCaptureDevice.cpp @@ -0,0 +1,634 @@ +#include "Logging.h" +#include "VideoCaptureDevice.h" + +#include +#include + +struct VideoCaptureReceiverFilter : winrt::implements +{ + FILTER_STATE _state = State_Stopped; + IFilterGraph* _graph = nullptr; + wil::com_ptr_nothrow _videoReceiverPin; + + ULONG STDMETHODCALLTYPE GetMiscFlags() override { return AM_FILTER_MISC_FLAGS_IS_RENDERER; } + + HRESULT STDMETHODCALLTYPE GetClassID(CLSID*) override { return E_NOTIMPL; } + + HRESULT STDMETHODCALLTYPE Stop() override + { + _state = State_Stopped; + return S_OK; + } + + HRESULT STDMETHODCALLTYPE Pause() override + { + _state = State_Paused; + return S_OK; + } + + HRESULT STDMETHODCALLTYPE Run(REFERENCE_TIME) override + { + _state = State_Running; + return S_OK; + } + + HRESULT STDMETHODCALLTYPE GetState(DWORD, FILTER_STATE* outState) override + { + *outState = _state; + return S_OK; + } + + HRESULT STDMETHODCALLTYPE GetSyncSource(IReferenceClock** outRefClock) override + { + *outRefClock = nullptr; + return NOERROR; + } + + HRESULT STDMETHODCALLTYPE SetSyncSource(IReferenceClock*) override { return S_OK; } + + HRESULT STDMETHODCALLTYPE EnumPins(IEnumPins** ppEnum) override + { + auto enumerator = winrt::make_self>(); + enumerator->_objects.emplace_back(_videoReceiverPin); + *ppEnum = enumerator.detach(); + + return S_OK; + } + + HRESULT STDMETHODCALLTYPE FindPin(LPCWSTR, IPin**) override { return E_NOTIMPL; } + + HRESULT STDMETHODCALLTYPE JoinFilterGraph(IFilterGraph* pGraph, LPCWSTR) override + { + _graph = pGraph; + return S_OK; + } + + HRESULT STDMETHODCALLTYPE QueryFilterInfo(FILTER_INFO* pInfo) override + { + std::copy(std::begin(NAME), std::end(NAME), pInfo->achName); + if (_graph) + { + pInfo->pGraph = _graph; + _graph->AddRef(); + } + return S_OK; + } + + HRESULT STDMETHODCALLTYPE QueryVendorInfo(LPWSTR* pVendorInfo) override + { + auto info = static_cast(CoTaskMemAlloc(sizeof(VENDOR))); + std::copy(std::begin(VENDOR), std::end(VENDOR), info); + *pVendorInfo = info; + return S_OK; + } + + virtual ~VideoCaptureReceiverFilter() = default; + + constexpr static inline wchar_t NAME[] = L"PowerToysVCMCaptureFilter"; + constexpr static inline wchar_t VENDOR[] = L"Microsoft Corporation"; +}; + +struct VideoCaptureReceiverPin : winrt::implements +{ + VideoCaptureReceiverFilter* _owningFilter = nullptr; + unique_media_type_ptr _expectedMediaType; + wil::com_ptr_nothrow _captureInputPin; + unique_media_type_ptr _inputCaptureMediaType; + std::atomic_bool _flushing = false; + VideoCaptureDevice::callback_t _frameCallback; + + wil::com_ptr_nothrow _allocator; + + VideoCaptureReceiverPin(unique_media_type_ptr mediaType, VideoCaptureReceiverFilter* filter) : + _expectedMediaType{ std::move(mediaType) }, _owningFilter{ filter } + { + } + + HRESULT STDMETHODCALLTYPE Connect(IPin*, const AM_MEDIA_TYPE* pmt) override + { + if (_owningFilter->_state == State_Running) + { + return VFW_E_NOT_STOPPED; + } + + if (_captureInputPin) + { + return VFW_E_ALREADY_CONNECTED; + } + + if (!pmt || pmt->majortype == GUID_NULL) + { + return S_OK; + } + + if (pmt->majortype != _expectedMediaType->majortype || pmt->subtype != _expectedMediaType->subtype) + { + return S_FALSE; + } + + return S_OK; + } + + HRESULT STDMETHODCALLTYPE ReceiveConnection(IPin* pConnector, const AM_MEDIA_TYPE* pmt) override + { + if (!pConnector || !pmt) + { + return E_POINTER; + } + + if (_captureInputPin) + { + return VFW_E_ALREADY_CONNECTED; + } + + if (_owningFilter->_state != State_Stopped) + { + return VFW_E_NOT_STOPPED; + } + + if (QueryAccept(pmt) != S_OK) + { + return VFW_E_TYPE_NOT_ACCEPTED; + } + + _captureInputPin = pConnector; + _inputCaptureMediaType = CopyMediaType(pmt); + + return S_OK; + } + + HRESULT STDMETHODCALLTYPE Disconnect() override + { + _allocator.reset(); + _captureInputPin.reset(); + _inputCaptureMediaType.reset(); + return S_OK; + } + + HRESULT STDMETHODCALLTYPE ConnectedTo(IPin** pPin) override + { + if (!_captureInputPin) + { + return VFW_E_NOT_CONNECTED; + } + + _captureInputPin.copy_to(pPin); + return S_OK; + } + + HRESULT STDMETHODCALLTYPE ConnectionMediaType(AM_MEDIA_TYPE* pmt) override + { + if (!pmt) + { + return E_POINTER; + } + + if (!_inputCaptureMediaType) + { + return VFW_E_NOT_CONNECTED; + } + + *pmt = *CopyMediaType(_inputCaptureMediaType).release(); + return S_OK; + } + + HRESULT STDMETHODCALLTYPE QueryPinInfo(PIN_INFO* pInfo) override + { + if (!pInfo) + { + return E_POINTER; + } + + pInfo->pFilter = _owningFilter; + if (_owningFilter) + { + _owningFilter->AddRef(); + } + + pInfo->dir = PINDIR_INPUT; + std::copy(std::begin(NAME), std::end(NAME), pInfo->achName); + return S_OK; + } + + HRESULT STDMETHODCALLTYPE QueryDirection(PIN_DIRECTION* pPinDir) override + { + if (!pPinDir) + { + return E_POINTER; + } + + *pPinDir = PINDIR_INPUT; + return S_OK; + } + + HRESULT STDMETHODCALLTYPE QueryId(LPWSTR* lpId) override + { + if (!lpId) + { + return E_POINTER; + } + + *lpId = static_cast(CoTaskMemAlloc(sizeof(NAME))); + + std::copy(std::begin(NAME), std::end(NAME), *lpId); + return S_OK; + } + + HRESULT STDMETHODCALLTYPE QueryAccept(const AM_MEDIA_TYPE* pmt) override + { + if (!pmt) + { + return E_POINTER; + } + + if (pmt->majortype != _expectedMediaType->majortype || pmt->subtype != _expectedMediaType->subtype) + { + return S_FALSE; + } + + if (_captureInputPin) + { + _inputCaptureMediaType.reset(const_cast(pmt)); + } + + return S_OK; + } + + HRESULT STDMETHODCALLTYPE EnumMediaTypes(IEnumMediaTypes** ppEnum) override + { + if (!ppEnum) + { + return E_POINTER; + } + + auto enumerator = winrt::make_self(); + enumerator->_objects.emplace_back(CopyMediaType(_expectedMediaType)); + *ppEnum = enumerator.detach(); + + return S_OK; + } + + HRESULT STDMETHODCALLTYPE QueryInternalConnections(IPin**, ULONG*) override { return E_NOTIMPL; } + + HRESULT STDMETHODCALLTYPE EndOfStream() override { return S_OK; } + + HRESULT STDMETHODCALLTYPE BeginFlush() override + { + _flushing = true; + return S_OK; + } + + HRESULT STDMETHODCALLTYPE EndFlush() override + { + _flushing = false; + return S_OK; + } + + HRESULT STDMETHODCALLTYPE NewSegment(REFERENCE_TIME, REFERENCE_TIME, double) override { return S_OK; } + + HRESULT STDMETHODCALLTYPE GetAllocator(IMemAllocator** allocator) override + { + VERBOSE_LOG; + if (!_allocator) + { + return VFW_E_NO_ALLOCATOR; + } + + _allocator.copy_to(allocator); + return S_OK; + } + + HRESULT STDMETHODCALLTYPE NotifyAllocator(IMemAllocator* allocator, BOOL readOnly) override + { + VERBOSE_LOG; + LOG(readOnly ? "Allocator READONLY: true" : "Allocator READONLY: false"); + _allocator = allocator; + return S_OK; + } + + HRESULT STDMETHODCALLTYPE GetAllocatorRequirements(ALLOCATOR_PROPERTIES*) override { return E_NOTIMPL; } + + HRESULT STDMETHODCALLTYPE Receive(IMediaSample* pSample) override + { + if (_flushing) + { + return S_FALSE; + } + + if (!pSample) + { + return E_POINTER; + } + + if (pSample && _frameCallback) + { + _frameCallback(pSample); + } + + return S_OK; + } + + HRESULT STDMETHODCALLTYPE ReceiveMultiple(IMediaSample** pSamples, long nSamples, long* nSamplesProcessed) override + { + if (!pSamples && nSamples) + { + return E_POINTER; + } + + if (_flushing) + { + return S_FALSE; + } + + for (long i = 0; i < nSamples; i++) + { + Receive(pSamples[i]); + } + + *nSamplesProcessed = nSamples; + return S_OK; + } + + HRESULT STDMETHODCALLTYPE ReceiveCanBlock() override { return S_FALSE; } + + virtual ~VideoCaptureReceiverPin() = default; + + constexpr static inline wchar_t NAME[] = L"PowerToysVCMCapturePin"; +}; + +constexpr long MINIMAL_FPS_ALLOWED = 29; + +const char* GetMediaSubTypeString(const GUID& guid) +{ + if (guid == MEDIASUBTYPE_RGB24) + { + return "MEDIASUBTYPE_RGB24"; + } + + if (guid == MEDIASUBTYPE_YUY2) + { + return "MEDIASUBTYPE_YUY2"; + } + + if (guid == MEDIASUBTYPE_MJPG) + { + return "MEDIASUBTYPE_MJPG"; + } + + if (guid == MEDIASUBTYPE_NV12) + { + return "MEDIASUBTYPE_NV12"; + } + + return "MEDIASUBTYPE_UNKNOWN"; +} + +std::optional SelectBestMediaType(wil::com_ptr_nothrow& pin) +{ + VERBOSE_LOG; + wil::com_ptr_nothrow mediaTypeEnum; + if (pin->EnumMediaTypes(&mediaTypeEnum); !mediaTypeEnum) + { + return std::nullopt; + } + + ULONG _ = 0; + VideoStreamFormat bestFormat; + unique_media_type_ptr mt; + while (mediaTypeEnum->Next(1, wil::out_param(mt), &_) == S_OK) + { + if (mt->majortype != MEDIATYPE_Video) + { + continue; + } + + auto format = reinterpret_cast(mt->pbFormat); + if (!format || !format->AvgTimePerFrame) + { + LOG("VideoInfoHeader not found"); + continue; + } + + const auto formatAvgFPS = 10000000LL / format->AvgTimePerFrame; + if (format->AvgTimePerFrame > bestFormat.avgFrameTime || formatAvgFPS < MINIMAL_FPS_ALLOWED) + { + continue; + } + + if (format->bmiHeader.biWidth < bestFormat.width || format->bmiHeader.biHeight < bestFormat.height) + { + continue; + } + + if (mt->subtype != MEDIASUBTYPE_YUY2 && mt->subtype != MEDIASUBTYPE_MJPG && mt->subtype != MEDIASUBTYPE_RGB24) + { + OLECHAR* guidString; + StringFromCLSID(mt->subtype, &guidString); + LOG("Skipping mediatype due to unsupported subtype: "); + LOG(guidString); + ::CoTaskMemFree(guidString); + continue; + } + + bestFormat.avgFrameTime = format->AvgTimePerFrame; + bestFormat.width = format->bmiHeader.biWidth; + bestFormat.height = format->bmiHeader.biHeight; + bestFormat.mediaType = std::move(mt); + } + + if (!bestFormat.mediaType) + { + LOG(L"Couldn't select a suitable media format"); + return std::nullopt; + } + + char selectedFormat[512]{}; + sprintf_s(selectedFormat, "Selected media format: %s %ldx%ld %lld fps", GetMediaSubTypeString(bestFormat.mediaType->subtype), bestFormat.width, bestFormat.height, 10000000LL / bestFormat.avgFrameTime); + LOG(selectedFormat); + + return std::move(bestFormat); +} + +std::vector VideoCaptureDevice::ListAll() +{ + std::vector devices; + auto enumeratorFactory = wil::CoCreateInstanceNoThrow(CLSID_SystemDeviceEnum); + if (!enumeratorFactory) + { + LOG("Couldn't create devenum factory"); + return devices; + } + + wil::com_ptr_nothrow enumMoniker; + enumeratorFactory->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, &enumMoniker, CDEF_DEVMON_PNP_DEVICE); + if (!enumMoniker) + { + LOG("Couldn't create class enumerator"); + return devices; + } + + ULONG _ = 0; + wil::com_ptr_nothrow moniker; + while (enumMoniker->Next(1, &moniker, &_) == S_OK) + { + LOG("Inspecting moniker"); + VideoCaptureDeviceInfo deviceInfo; + + wil::com_ptr_nothrow propertyData; + moniker->BindToStorage(nullptr, nullptr, IID_IPropertyBag, reinterpret_cast(&propertyData)); + if (!propertyData) + { + LOG("BindToStorage failed"); + continue; + } + + wil::unique_variant propVal; + propVal.vt = VT_BSTR; + + if (FAILED(propertyData->Read(L"FriendlyName", &propVal, nullptr))) + { + LOG("Couldn't obtain FriendlyName property"); + continue; + } + + deviceInfo.friendlyName = { propVal.bstrVal, SysStringLen(propVal.bstrVal) }; + LOG(deviceInfo.friendlyName); + + propVal.reset(); + propVal.vt = VT_BSTR; + + if (FAILED(propertyData->Read(L"DevicePath", &propVal, nullptr))) + { + LOG("Couldn't obtain DevicePath property"); + continue; + } + deviceInfo.devicePath = { propVal.bstrVal, SysStringLen(propVal.bstrVal) }; + + wil::com_ptr_nothrow filter; + moniker->BindToObject(nullptr, nullptr, IID_IBaseFilter, reinterpret_cast(&filter)); + if (!filter) + { + LOG("Couldn't BindToObject"); + continue; + } + + wil::com_ptr_nothrow pinsEnum; + if (FAILED(filter->EnumPins(&pinsEnum))) + { + LOG("BindToObject EnumPins"); + continue; + } + + wil::com_ptr_nothrow pin; + while (pinsEnum->Next(1, &pin, &_) == S_OK) + { + LOG("Inspecting pin"); + // Skip pins which do not belong to capture category + GUID category{}; + DWORD __; + if (auto props = pin.try_copy(); + !props || + FAILED(props->Get(AMPROPSETID_Pin, AMPROPERTY_PIN_CATEGORY, nullptr, 0, &category, sizeof(GUID), &__)) || + category != PIN_CATEGORY_CAPTURE) + { + continue; + } + + // Skip non-output pins + if (PIN_DIRECTION direction = {}; FAILED(pin->QueryDirection(&direction)) || direction != PINDIR_OUTPUT) + { + continue; + } + + LOG("Found a pin of suitable category and direction, selecting format"); + auto bestFormat = SelectBestMediaType(pin); + if (!bestFormat) + { + continue; + } + + deviceInfo.captureOutputPin = std::move(pin); + deviceInfo.bestFormat = std::move(bestFormat.value()); + deviceInfo.captureOutputFilter = std::move(filter); + devices.emplace_back(std::move(deviceInfo)); + } + } + + return devices; +} + +std::optional VideoCaptureDevice::Create(VideoCaptureDeviceInfo&& vdi, callback_t callback) +{ + VERBOSE_LOG; + VideoCaptureDevice result; + + result._graph = wil::CoCreateInstanceNoThrow(CLSID_FilterGraph); + result._builder = wil::CoCreateInstanceNoThrow(CLSID_CaptureGraphBuilder2); + if (!result._graph || !result._builder) + { + return std::nullopt; + } + + if (FAILED(result._builder->SetFiltergraph(result._graph.get()))) + { + return std::nullopt; + } + + result._control = result._graph.try_query(); + if (!result._control) + { + return std::nullopt; + } + + auto pinConfig = vdi.captureOutputPin.try_query(); + if (!pinConfig) + { + return std::nullopt; + } + + if (FAILED(pinConfig->SetFormat(vdi.bestFormat.mediaType.get()))) + { + return std::nullopt; + } + + auto captureInputFilter = winrt::make_self(); + auto receiverPin = winrt::make_self(std::move(vdi.bestFormat.mediaType), captureInputFilter.get()); + receiverPin->_frameCallback = std::move(callback); + captureInputFilter->_videoReceiverPin.attach(receiverPin.get()); + auto detachReceiverPin = wil::scope_exit([&receiverPin]() { receiverPin.detach(); }); + + if (FAILED(result._graph->AddFilter(captureInputFilter.get(), nullptr))) + { + return std::nullopt; + } + + if (FAILED(result._graph->AddFilter(vdi.captureOutputFilter.get(), nullptr))) + { + return std::nullopt; + } + + if (FAILED(result._graph->ConnectDirect(vdi.captureOutputPin.get(), captureInputFilter->_videoReceiverPin.get(), nullptr))) + { + return std::nullopt; + } + + result._allocator = receiverPin->_allocator; + return std::make_optional(std::move(result)); +} + +bool VideoCaptureDevice::StartCapture() +{ + VERBOSE_LOG; + return SUCCEEDED(_control->Run()); +} + +bool VideoCaptureDevice::StopCapture() +{ + VERBOSE_LOG; + return SUCCEEDED(_control->Stop()); +} + +VideoCaptureDevice::~VideoCaptureDevice() +{ + StopCapture(); +} diff --git a/src/modules/videoconference/VideoConferenceProxyFilter/VideoCaptureDevice.h b/src/modules/videoconference/VideoConferenceProxyFilter/VideoCaptureDevice.h new file mode 100644 index 0000000000..3af63691a6 --- /dev/null +++ b/src/modules/videoconference/VideoConferenceProxyFilter/VideoCaptureDevice.h @@ -0,0 +1,62 @@ +#pragma once +#include +#define WIN32_LEAN_AND_MEAN + +#include +#include + +#include + +#include +#include +#include +#include + +#include "DirectShowUtils.h" + +struct VideoStreamFormat +{ + long width = 0; + long height = 0; + REFERENCE_TIME avgFrameTime = std::numeric_limits::max(); + unique_media_type_ptr mediaType; + + VideoStreamFormat() = default; + + VideoStreamFormat(const VideoStreamFormat&) = delete; + VideoStreamFormat& operator=(const VideoStreamFormat&) = delete; + + VideoStreamFormat(VideoStreamFormat&&) = default; + VideoStreamFormat& operator=(VideoStreamFormat&&) = default; +}; + +struct VideoCaptureDeviceInfo +{ + std::wstring friendlyName; + std::wstring devicePath; + wil::com_ptr_nothrow captureOutputPin; + wil::com_ptr_nothrow captureOutputFilter; + VideoStreamFormat bestFormat; +}; + +class VideoCaptureDevice final +{ +public: + wil::com_ptr_nothrow _allocator; + + using callback_t = std::function; + + static std::vector ListAll(); + static std::optional Create(VideoCaptureDeviceInfo&& vdi, callback_t callback); + + bool StartCapture(); + bool StopCapture(); + + ~VideoCaptureDevice(); + +private: + wil::com_ptr_nothrow _graph; + wil::com_ptr_nothrow _builder; + wil::com_ptr_nothrow _control; + callback_t _callback; +}; diff --git a/src/modules/videoconference/VideoConferenceProxyFilter/VideoCaptureProxyFilter.cpp b/src/modules/videoconference/VideoConferenceProxyFilter/VideoCaptureProxyFilter.cpp new file mode 100644 index 0000000000..bfd2c9e088 --- /dev/null +++ b/src/modules/videoconference/VideoConferenceProxyFilter/VideoCaptureProxyFilter.cpp @@ -0,0 +1,919 @@ +#include "VideoCaptureProxyFilter.h" + +#include "VideoCaptureDevice.h" +#include +#include +#include +#include + +constexpr static inline wchar_t FILTER_NAME[] = L"PowerToysVCMProxyFilter"; +constexpr static inline wchar_t PIN_NAME[] = L"PowerToysVCMProxyPIN"; +constexpr static inline wchar_t VENDOR[] = L"Microsoft Corporation"; + +namespace +{ + constexpr float initialJpgQuality = 0.5f; + constexpr std::array overlayColor = { 0, 0, 0 }; + // clang-format off + unsigned char bmpPixelData[58] = { + 0x42, 0x4D, 0x3A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0x00, + 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, + 0x00, 0x00, 0xC4, 0x0E, 0x00, 0x00, 0xC4, 0x0E, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, overlayColor[0], overlayColor[1], overlayColor[2], 0x00 + }; + // clang-format on +} + +wil::com_ptr_nothrow VideoCaptureProxyPin::FindAllocator() +{ + auto allocator = GetPinAllocator(_connectedInputPin); + if (!allocator && _owningFilter->_captureDevice) + { + allocator = _owningFilter->_captureDevice->_allocator; + } + + return allocator; +} + +wil::com_ptr_nothrow LoadImageAsSample(wil::com_ptr_nothrow imageStream, + IMFMediaType* sampleMediaType, + const float quality) noexcept; +bool ReencodeJPGImage(BYTE* imageBuf, const DWORD imageSize, DWORD& reencodedSize); + +HRESULT VideoCaptureProxyPin::Connect(IPin* pReceivePin, const AM_MEDIA_TYPE*) +{ + if (!pReceivePin) + { + LOG("VideoCaptureProxyPin::Connect FAILED pReceivePin"); + return E_POINTER; + } + + if (_owningFilter->_state == State_Running) + { + LOG("VideoCaptureProxyPin::Connect FAILED _owningFilter->_state"); + return VFW_E_NOT_STOPPED; + } + + if (_connectedInputPin) + { + LOG("VideoCaptureProxyPin::Connect FAILED _connectedInputPin"); + return VFW_E_ALREADY_CONNECTED; + } + + if (FAILED(pReceivePin->ReceiveConnection(this, _mediaFormat.get()))) + { + LOG("VideoCaptureProxyPin::Connect FAILED pReceivePin->ReceiveConnection"); + return E_POINTER; + } + + _connectedInputPin = pReceivePin; + + auto memInput = _connectedInputPin.try_query(); + if (!memInput) + { + LOG("VideoCaptureProxyPin::Connect FAILED _connectedInputPin.try_query"); + return VFW_E_NO_TRANSPORT; + } + + auto allocator = FindAllocator(); + if (allocator == nullptr) + { + LOG("VideoCaptureProxyPin::Connect FAILED FindAllocator"); + return VFW_E_NO_TRANSPORT; + } + + if (FAILED(memInput->NotifyAllocator(allocator.get(), false))) + { + LOG("VideoCaptureProxyPin::Connect FAILED memInput->NotifyAllocator"); + return VFW_E_NO_TRANSPORT; + } + + return S_OK; +} + +HRESULT VideoCaptureProxyPin::ReceiveConnection(IPin*, const AM_MEDIA_TYPE*) +{ + return S_OK; +} + +HRESULT VideoCaptureProxyPin::Disconnect(void) +{ + if (!_connectedInputPin) + { + LOG("VideoCaptureProxyPin::Disconnect FAILED _connectedInputPin"); + return S_FALSE; + } + + _connectedInputPin.reset(); + return S_OK; +} + +HRESULT VideoCaptureProxyPin::ConnectedTo(IPin** pPin) +{ + if (!_connectedInputPin) + { + *pPin = nullptr; + return VFW_E_NOT_CONNECTED; + } + + _connectedInputPin.try_copy_to(pPin); + return S_OK; +} + +HRESULT VideoCaptureProxyPin::ConnectionMediaType(AM_MEDIA_TYPE* pmt) +{ + if (!_connectedInputPin) + { + LOG("VideoCaptureProxyPin::ConnectionMediaType FAILED _connectedInputPin"); + return VFW_E_NOT_CONNECTED; + } + + *pmt = *CopyMediaType(_mediaFormat).release(); + return S_OK; +} + +HRESULT VideoCaptureProxyPin::QueryPinInfo(PIN_INFO* pInfo) +{ + if (!pInfo) + { + LOG("VideoCaptureProxyPin::QueryPinInfo FAILED pInfo"); + return E_POINTER; + } + + pInfo->pFilter = _owningFilter; + if (_owningFilter) + { + _owningFilter->AddRef(); + } + + if (_mediaFormat->majortype == MEDIATYPE_Video) + { + std::copy(std::begin(PIN_NAME), std::end(PIN_NAME), pInfo->achName); + } + + pInfo->dir = PINDIR_OUTPUT; + return S_OK; +} + +HRESULT VideoCaptureProxyPin::QueryDirection(PIN_DIRECTION* pPinDir) +{ + if (!pPinDir) + { + LOG("VideoCaptureProxyPin::QueryDirection FAILED pPinDir"); + return E_POINTER; + } + + *pPinDir = PINDIR_OUTPUT; + return S_OK; +} + +HRESULT VideoCaptureProxyPin::QueryId(LPWSTR* Id) +{ + if (!Id) + { + LOG("VideoCaptureProxyPin::QueryId FAILED Id"); + return E_POINTER; + } + + *Id = static_cast(CoTaskMemAlloc(sizeof(PIN_NAME))); + std::copy(std::begin(PIN_NAME), std::end(PIN_NAME), *Id); + return S_OK; +} + +HRESULT VideoCaptureProxyPin::QueryAccept(const AM_MEDIA_TYPE*) +{ + return S_OK; +} + +HRESULT VideoCaptureProxyPin::EnumMediaTypes(IEnumMediaTypes** ppEnum) +{ + if (!ppEnum) + { + LOG("VideoCaptureProxyPin::EnumMediaTypes FAILED ppEnum"); + return E_POINTER; + } + + auto enumerator = winrt::make_self(); + enumerator->_objects.emplace_back(CopyMediaType(_mediaFormat)); + *ppEnum = enumerator.detach(); + + return S_OK; +} + +HRESULT VideoCaptureProxyPin::QueryInternalConnections(IPin**, ULONG*) +{ + return E_NOTIMPL; +} + +HRESULT VideoCaptureProxyPin::EndOfStream(void) +{ + return S_OK; +} + +HRESULT VideoCaptureProxyPin::BeginFlush(void) +{ + _flushing = true; + return S_OK; +} + +HRESULT VideoCaptureProxyPin::EndFlush(void) +{ + _flushing = false; + return S_OK; +} + +HRESULT VideoCaptureProxyPin::NewSegment(REFERENCE_TIME, REFERENCE_TIME, double) +{ + return S_OK; +} + +HRESULT VideoCaptureProxyPin::SetFormat(AM_MEDIA_TYPE* pmt) +{ + if (pmt == nullptr) + { + return S_OK; + } + + _mediaFormat = CopyMediaType(pmt); + return S_OK; +} + +HRESULT VideoCaptureProxyPin::GetFormat(AM_MEDIA_TYPE** ppmt) +{ + if (!ppmt) + { + LOG("VideoCaptureProxyPin::GetFormat FAILED ppmt"); + return E_POINTER; + } + + *ppmt = CopyMediaType(_mediaFormat).release(); + return S_OK; +} + +HRESULT VideoCaptureProxyPin::GetNumberOfCapabilities(int* piCount, int* piSize) +{ + if (!piCount || !piSize) + { + LOG("VideoCaptureProxyPin::GetNumberOfCapabilities FAILED piCount || piSize"); + return E_POINTER; + } + + *piCount = 1; + *piSize = sizeof(VIDEO_STREAM_CONFIG_CAPS); + return S_OK; +} + +HRESULT VideoCaptureProxyPin::GetStreamCaps(int iIndex, AM_MEDIA_TYPE** ppmt, BYTE* pSCC) +{ + if (!ppmt || !pSCC) + { + LOG("VideoCaptureProxyPin::GetStreamCaps FAILED ppmt || pSCC"); + return E_POINTER; + } + + if (iIndex != 0) + { + LOG("VideoCaptureProxyPin::GetStreamCaps FAILED iIndex"); + return S_FALSE; + } + + VIDEOINFOHEADER* vih = reinterpret_cast(_mediaFormat->pbFormat); + + VIDEO_STREAM_CONFIG_CAPS caps{}; + caps.guid = FORMAT_VideoInfo; + caps.MinFrameInterval = vih->AvgTimePerFrame; + caps.MaxFrameInterval = vih->AvgTimePerFrame; + caps.MinOutputSize.cx = vih->bmiHeader.biWidth; + caps.MinOutputSize.cy = vih->bmiHeader.biHeight; + caps.MaxOutputSize = caps.MinOutputSize; + caps.InputSize = caps.MinOutputSize; + caps.MinCroppingSize = caps.MinOutputSize; + caps.MaxCroppingSize = caps.MinOutputSize; + caps.CropGranularityX = vih->bmiHeader.biWidth; + caps.CropGranularityY = vih->bmiHeader.biHeight; + caps.MinBitsPerSecond = vih->dwBitRate; + caps.MaxBitsPerSecond = caps.MinBitsPerSecond; + + *ppmt = CopyMediaType(_mediaFormat).release(); + + const auto caps_begin = reinterpret_cast(&caps); + std::copy(caps_begin, caps_begin + sizeof(caps), pSCC); + return S_OK; +} + +HRESULT VideoCaptureProxyPin::Set(REFGUID, DWORD, LPVOID, DWORD, LPVOID, DWORD) +{ + return E_NOTIMPL; +} + +HRESULT VideoCaptureProxyPin::Get( + REFGUID guidPropSet, + DWORD dwPropID, + LPVOID, + DWORD, + LPVOID pPropData, + DWORD cbPropData, + DWORD* pcbReturned) +{ + if (guidPropSet != AMPROPSETID_Pin) + { + LOG("VideoCaptureProxyPin::Get FAILED guidPropSet"); + return E_PROP_SET_UNSUPPORTED; + } + + if (dwPropID != AMPROPERTY_PIN_CATEGORY) + { + LOG("VideoCaptureProxyPin::Get FAILED dwPropID"); + return E_PROP_ID_UNSUPPORTED; + } + + if (!pPropData) + { + LOG("VideoCaptureProxyPin::Get FAILED pPropData || pcbReturned"); + return E_POINTER; + } + + if (pcbReturned) + { + *pcbReturned = sizeof(GUID); + } + + if (cbPropData < sizeof(GUID)) + { + LOG("VideoCaptureProxyPin::Get FAILED cbPropData"); + return E_UNEXPECTED; + } + + *(GUID*)pPropData = PIN_CATEGORY_CAPTURE; + + LOG("VideoCaptureProxyPin::Get SUCCESS"); + return S_OK; +} + +HRESULT VideoCaptureProxyPin::QuerySupported(REFGUID guidPropSet, DWORD dwPropID, DWORD* pTypeSupport) +{ + if (guidPropSet != AMPROPSETID_Pin) + { + LOG("VideoCaptureProxyPin::QuerySupported FAILED guidPropSet"); + return E_PROP_SET_UNSUPPORTED; + } + + if (dwPropID != AMPROPERTY_PIN_CATEGORY) + { + LOG("VideoCaptureProxyPin::QuerySupported FAILED dwPropID"); + return E_PROP_ID_UNSUPPORTED; + } + + if (pTypeSupport) + { + *pTypeSupport = KSPROPERTY_SUPPORT_GET; + } + + return S_OK; +} + +long GetImageSize(wil::com_ptr_nothrow& image) +{ + if (!image) + { + return 0; + } + + DWORD imageSize = 0; + wil::com_ptr_nothrow imageBuf; + + OK_OR_BAIL(image->GetBufferByIndex(0, &imageBuf)); + OK_OR_BAIL(imageBuf->GetCurrentLength(&imageSize)); + return imageSize; +} + +void ReencodeFrame(IMediaSample* frame) +{ + BYTE* frameData = nullptr; + frame->GetPointer(&frameData); + if (!frameData) + { + LOG("VideoCaptureProxyPin::ReencodeFrame FAILED frameData"); + return; + } + const DWORD frameSize = frame->GetSize(); + DWORD reencodedSize = 0; + if (!ReencodeJPGImage(frameData, frameSize, reencodedSize)) + { + LOG("VideoCaptureProxyPin::ReencodeJPGImage FAILED"); + return; + } + frame->SetActualDataLength(reencodedSize); +} + +bool OverwriteFrame(IMediaSample* frame, wil::com_ptr_nothrow& image) +{ + if (!image) + { + return false; + } + + BYTE* frameData = nullptr; + frame->GetPointer(&frameData); + if (!frameData) + { + LOG("VideoCaptureProxyPin::OverwriteFrame FAILED frameData"); + return false; + } + + wil::com_ptr_nothrow imageBuf; + const DWORD frameSize = frame->GetSize(); + + image->GetBufferByIndex(0, &imageBuf); + if (!imageBuf) + { + LOG("VideoCaptureProxyPin::OverwriteFrame FAILED imageBuf"); + return false; + } + + BYTE* imageData = nullptr; + DWORD _ = 0, imageSize = 0; + imageBuf->Lock(&imageData, &_, &imageSize); + if (!imageData) + { + LOG("VideoCaptureProxyPin::OverwriteFrame FAILED imageData"); + return false; + } + + if (imageSize > frameSize && failed(frame->SetActualDataLength(imageSize))) + { + char buf[512]{}; + sprintf_s(buf, "VideoCaptureProxyPin::OverwriteFrame FAILED overlay image size %lu is larger than frame size %lu", imageSize, frameSize); + LOG(buf); + imageBuf->Unlock(); + return false; + } + + std::copy(imageData, imageData + imageSize, frameData); + imageBuf->Unlock(); + frame->SetActualDataLength(imageSize); + + return true; +} + +//#define DEBUG_FRAME_DATA +//#define DEBUG_OVERWRITE_FRAME +//#define DEBUG_REENCODE_JPG_DATA + +#if defined(DEBUG_OVERWRITE_FRAME) +void DebugOverwriteFrame(IMediaSample* frame, std::string_view filepath) +{ + std::ifstream file{ filepath.data(), std::ios::binary }; + std::streampos fileSize = 0; + fileSize = file.tellg(); + file.seekg(0, std::ios::end); + fileSize = file.tellg() - fileSize; + + BYTE* frameData = nullptr; + if (!frame) + { + LOG("null frame provided"); + return; + } + frame->GetPointer(&frameData); + const DWORD frameSize = frame->GetSize(); + + if (fileSize > frameSize || !frameData) + { + LOG("frame can't be filled with data"); + return; + } + file.read((char*)frameData, fileSize); + frame->SetActualDataLength((long)fileSize); + LOG("DebugOverwriteFrame success"); +} + +#endif + +#if defined(DEBUG_FRAME_DATA) +#include + +namespace fs = std::filesystem; + +void DumpSample(IMediaSample* frame, const std::string_view filename) +{ + BYTE* data = nullptr; + frame->GetPointer(&data); + if (!data) + { + LOG("Couldn't get sample pointer"); + return; + } + const long nBytes = frame->GetActualDataLength(); + std::ofstream file{ fs::temp_directory_path() / filename, std::ios::binary }; + file.write((const char*)data, nBytes); +} +#endif + +VideoCaptureProxyFilter::VideoCaptureProxyFilter() : + _worker_thread{ + std::thread{ + [this]() { + using namespace std::chrono_literals; + const auto uninitializedSleepInterval = 15ms; + std::vector lowerJpgQualityModes = { 0.1f, 0.25f }; + while (!_shutdown_request) + { + std::unique_lock lock{ _worker_mutex }; + _worker_cv.wait(lock, [this] { return _pending_frame != nullptr || _shutdown_request; }); + + if (!_outPin || !_outPin->_connectedInputPin) + { + lock.unlock(); + std::this_thread::sleep_for(uninitializedSleepInterval); + continue; + } + + auto input = _outPin->_connectedInputPin.try_query(); + if (!input) + { + continue; + } + + IMediaSample* sample = _pending_frame; + if (!sample) + { + continue; + } +#if defined(DEBUG_FRAME_DATA) + static bool realFrameSaved = false; + if (!realFrameSaved) + { + DumpSample(sample, "PowerToysVCMRealFrame.binary"); + realFrameSaved = true; + } +#endif + auto newSettings = SyncCurrentSettings(); + if (newSettings.webcamDisabled) + { +#if !defined(DEBUG_OVERWRITE_FRAME) + bool overwritten = OverwriteFrame(_pending_frame, _overlayImage ? _overlayImage : _blankImage); + while (!overwritten && _overlayImage) + { + _overlayImage.reset(); + newSettings = SyncCurrentSettings(); + if (!lowerJpgQualityModes.empty() && newSettings.overlayImage) + { + const float quality = lowerJpgQualityModes.back(); + lowerJpgQualityModes.pop_back(); + char buf[512]{}; + sprintf_s(buf, "Reload overlay image with quality %f", quality); + LOG(buf); + _overlayImage = LoadImageAsSample(newSettings.overlayImage, _targetMediaType.get(), quality); + overwritten = OverwriteFrame(_pending_frame, _overlayImage); + } + else + { + LOG("Couldn't overwrite frame with image with all available quality modes."); + } + } +#if defined(DEBUG_FRAME_DATA) + static bool overlayFrameSaved = false; + if (!overlayFrameSaved && _overlayImage && overwritten) + { + DumpSample(sample, "PowerToysVCMOverlayImageFrame.binary"); + overlayFrameSaved = true; + } +#endif + if (!overwritten && !_overlayImage) + { + OverwriteFrame(_pending_frame, _blankImage); + } +#else + DebugOverwriteFrame(_pending_frame, "R:\\frame.data"); +#endif + } +#if defined(DEBUG_REENCODE_JPG_DATA) + else + { + GUID subtype{}; + _targetMediaType->GetGUID(MF_MT_SUBTYPE, &subtype); + if (subtype == MFVideoFormat_MJPG) + { + ReencodeFrame(_pending_frame); + } + } +#endif + + _pending_frame = nullptr; + input->Receive(sample); + sample->Release(); + } + } } + } +{ +} + +HRESULT VideoCaptureProxyFilter::Stop(void) +{ + if (_state != State_Stopped && _captureDevice) + { + _captureDevice->StopCapture(); + } + + _state = State_Stopped; + return S_OK; +} + +HRESULT VideoCaptureProxyFilter::Pause(void) +{ + if (_state == State_Stopped) + { + std::unique_lock lock{ _worker_mutex }; + + if (!_outPin) + { + LOG("VideoCaptureProxyPin::Pause FAILED _outPin"); + return VFW_E_NO_TRANSPORT; + } + + auto allocator = _outPin->FindAllocator(); + if (!allocator) + { + LOG("VideoCaptureProxyPin::Pause FAILED allocator"); + return VFW_E_NO_TRANSPORT; + } + + allocator->Commit(); + } + + _state = State_Paused; + return S_OK; +} + +HRESULT VideoCaptureProxyFilter::Run(REFERENCE_TIME) +{ + _state = State_Running; + if (_captureDevice) + { + _captureDevice->StartCapture(); + } + + return S_OK; +} + +HRESULT VideoCaptureProxyFilter::GetState(DWORD, FILTER_STATE* State) +{ + *State = _state; + return S_OK; +} + +HRESULT VideoCaptureProxyFilter::SetSyncSource(IReferenceClock* pClock) +{ + _clock = pClock; + return S_OK; +} + +HRESULT VideoCaptureProxyFilter::GetSyncSource(IReferenceClock** pClock) +{ + if (!pClock) + { + return E_POINTER; + } + _clock.try_copy_to(pClock); + return S_OK; +} + +GUID MapDShowSubtypeToMFT(const GUID& dshowSubtype) +{ + if (dshowSubtype == MEDIASUBTYPE_YUY2) + { + return MFVideoFormat_YUY2; + } + else if (dshowSubtype == MEDIASUBTYPE_MJPG) + { + return MFVideoFormat_MJPG; + } + else if (dshowSubtype == MEDIASUBTYPE_RGB24) + { + return MFVideoFormat_RGB24; + } + else + { + LOG("MapDShowSubtypeToMFT: Unsupported media type format provided!"); + return MFVideoFormat_MJPG; + } +} + +HRESULT VideoCaptureProxyFilter::EnumPins(IEnumPins** ppEnum) +{ + if (!ppEnum) + { + LOG("VideoCaptureProxyFilter::EnumPins null arg provided"); + return E_POINTER; + } + + std::unique_lock lock{ _worker_mutex }; + + // We cannot initialize capture device and outpin during VideoCaptureProxyFilter ctor + // since that results in a deadlock -> initializing now. + if (!_outPin) + { + LOG("VideoCaptureProxyFilter::EnumPins started pin initialization"); + const auto newSettings = SyncCurrentSettings(); + std::vector webcams; + webcams = VideoCaptureDevice::ListAll(); + if (webcams.empty()) + { + LOG("VideoCaptureProxyFilter::EnumPins no physical webcams found"); + return E_FAIL; + } + + std::optional selectedCamIdx; + for (size_t i = 0; i < size(webcams); ++i) + { + if (newSettings.newCameraName == webcams[i].friendlyName) + { + selectedCamIdx = i; + LOG("VideoCaptureProxyFilter::EnumPins webcam selected using settings"); + break; + } + } + + if (!selectedCamIdx) + { + for (size_t i = 0; i < size(webcams); ++i) + { + if (newSettings.newCameraName != CAMERA_NAME) + { + LOG("VideoCaptureProxyFilter::EnumPins webcam selected using first fit"); + selectedCamIdx = i; + break; + } + } + } + + if (!selectedCamIdx) + { + LOG("VideoCaptureProxyFilter::EnumPins FAILED webcam couldn't be selected"); + return E_FAIL; + } + + auto& webcam = webcams[*selectedCamIdx]; + auto pin = winrt::make_self(); + pin->_mediaFormat = CopyMediaType(webcam.bestFormat.mediaType); + pin->_owningFilter = this; + _outPin.attach(pin.detach()); + + auto frameCallback = [this](IMediaSample* sample) { + std::unique_lock lock{ _worker_mutex }; + sample->AddRef(); + _pending_frame = sample; + _worker_cv.notify_one(); + }; + + _targetMediaType.reset(); + MFCreateMediaType(&_targetMediaType); + _targetMediaType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video); + _targetMediaType->SetGUID(MF_MT_SUBTYPE, MapDShowSubtypeToMFT(webcam.bestFormat.mediaType->subtype)); + _targetMediaType->SetUINT32(MF_MT_INTERLACE_MODE, MFVideoInterlace_Progressive); + _targetMediaType->SetUINT32(MF_MT_ALL_SAMPLES_INDEPENDENT, TRUE); + MFSetAttributeSize( + _targetMediaType.get(), MF_MT_FRAME_SIZE, webcam.bestFormat.width, webcam.bestFormat.height); + MFSetAttributeRatio(_targetMediaType.get(), MF_MT_PIXEL_ASPECT_RATIO, 1, 1); + + _captureDevice = VideoCaptureDevice::Create(std::move(webcam), std::move(frameCallback)); + if (_captureDevice) + { + if (!_blankImage) + { + wil::com_ptr_nothrow blackBMPImage = SHCreateMemStream(bmpPixelData, sizeof(bmpPixelData)); + _blankImage = LoadImageAsSample(blackBMPImage, _targetMediaType.get(), initialJpgQuality); + } + + _overlayImage = LoadImageAsSample(newSettings.overlayImage, _targetMediaType.get(), initialJpgQuality); + LOG("VideoCaptureProxyFilter::EnumPins capture device created successfully"); + } + else + { + LOG("VideoCaptureProxyFilter::EnumPins FAILED couldn't create capture device"); + } + } + + auto enumerator = winrt::make_self>(); + enumerator->_objects.emplace_back(_outPin); + *ppEnum = enumerator.detach(); + return S_OK; +} + +HRESULT VideoCaptureProxyFilter::FindPin(LPCWSTR, IPin**) +{ + return E_NOTIMPL; +} + +HRESULT VideoCaptureProxyFilter::QueryFilterInfo(FILTER_INFO* pInfo) +{ + if (!pInfo) + { + LOG("VideoCaptureProxyPin::QueryFilterInfo FAILED pInfo"); + return E_POINTER; + } + + VERBOSE_LOG; + std::copy(std::begin(FILTER_NAME), std::end(FILTER_NAME), pInfo->achName); + + pInfo->pGraph = _graph; + if (_graph) + { + _graph->AddRef(); + } + + return S_OK; +} + +HRESULT VideoCaptureProxyFilter::JoinFilterGraph(IFilterGraph* pGraph, LPCWSTR) +{ + _graph = pGraph; + return S_OK; +} + +HRESULT VideoCaptureProxyFilter::QueryVendorInfo(LPWSTR* pVendorInfo) +{ + auto info = static_cast(CoTaskMemAlloc(sizeof(VENDOR))); + std::copy(std::begin(VENDOR), std::end(VENDOR), info); + *pVendorInfo = info; + return S_OK; +} + +HRESULT VideoCaptureProxyFilter::GetClassID(CLSID*) +{ + return E_NOTIMPL; +} + +ULONG VideoCaptureProxyFilter::GetMiscFlags(void) +{ + return AM_FILTER_MISC_FLAGS_IS_SOURCE; +} + +VideoCaptureProxyFilter::~VideoCaptureProxyFilter() +{ + VERBOSE_LOG; + _shutdown_request = true; + + _worker_cv.notify_one(); + _worker_thread.join(); +} + +VideoCaptureProxyFilter::SyncedSettings VideoCaptureProxyFilter::SyncCurrentSettings() +{ + SyncedSettings result; + if (!_settingsUpdateChannel.has_value()) + { + _settingsUpdateChannel = SerializedSharedMemory::open(CameraSettingsUpdateChannel::endpoint(), sizeof(CameraSettingsUpdateChannel), false); + } + + if (!_settingsUpdateChannel) + { + return result; + } + + _settingsUpdateChannel->access([this, &result](auto settingsMemory) { + auto settings = reinterpret_cast(settingsMemory._data); + bool cameraNameUpdated = false; + result.webcamDisabled = settings->useOverlayImage; + + settings->cameraInUse = true; + + if (settings->sourceCameraName.has_value()) + { + std::wstring_view newCameraNameView{ settings->sourceCameraName->data() }; + if (!_currentSourceCameraName.has_value() || *_currentSourceCameraName != newCameraNameView) + { + cameraNameUpdated = true; + result.newCameraName = newCameraNameView; + } + } + + if (!settings->overlayImageSize.has_value()) + { + return; + } + + if (settings->newOverlayImagePosted || !_overlayImage) + { + auto imageChannel = + SerializedSharedMemory::open(CameraOverlayImageChannel::endpoint(), *settings->overlayImageSize, true); + if (!imageChannel) + { + return; + } + + imageChannel->access([this, settings, &result](auto imageMemory) { + result.overlayImage = SHCreateMemStream(imageMemory._data, static_cast(imageMemory._size)); + if (!result.overlayImage) + { + return; + } + + settings->newOverlayImagePosted = false; + }); + } + }); + return result; +} diff --git a/src/modules/videoconference/VideoConferenceProxyFilter/VideoCaptureProxyFilter.h b/src/modules/videoconference/VideoConferenceProxyFilter/VideoCaptureProxyFilter.h new file mode 100644 index 0000000000..58f49f032a --- /dev/null +++ b/src/modules/videoconference/VideoConferenceProxyFilter/VideoCaptureProxyFilter.h @@ -0,0 +1,120 @@ +#pragma once + +#include + +#define WIN32_LEAN_AND_MEAN +#include +#include + +#include +#include +#include + +#include +#include + +#include "VideoCaptureDevice.h" + +#include +#include + +struct VideoCaptureProxyPin; +struct IMFSample; +struct IMFMediaType; + +inline const wchar_t CAMERA_NAME[] = L"PowerToys VideoConference Mute"; + +struct VideoCaptureProxyFilter : winrt::implements +{ + // BLOCK START: member accessed concurrently + wil::com_ptr_nothrow _outPin; + IMediaSample* _pending_frame = nullptr; + std::atomic_bool _shutdown_request = false; + std::optional _settingsUpdateChannel; + std::optional _currentSourceCameraName; + wil::com_ptr_nothrow _blankImage; + wil::com_ptr_nothrow _overlayImage; + wil::com_ptr_nothrow _targetMediaType; + // BLOCK END: member accessed concurrently + + std::mutex _worker_mutex; + std::condition_variable _worker_cv; + + FILTER_STATE _state = State_Stopped; + wil::com_ptr_nothrow _clock; + IFilterGraph* _graph = nullptr; + std::optional _captureDevice; + + std::thread _worker_thread; + + VideoCaptureProxyFilter(); + ~VideoCaptureProxyFilter(); + + struct SyncedSettings + { + bool webcamDisabled = false; + std::wstring newCameraName; + wil::com_ptr_nothrow overlayImage; + }; + + SyncedSettings SyncCurrentSettings(); + + HRESULT STDMETHODCALLTYPE Stop(void) override; + HRESULT STDMETHODCALLTYPE Pause(void) override; + HRESULT STDMETHODCALLTYPE Run(REFERENCE_TIME tStart) override; + HRESULT STDMETHODCALLTYPE GetState(DWORD dwMilliSecsTimeout, FILTER_STATE* State) override; + HRESULT STDMETHODCALLTYPE SetSyncSource(IReferenceClock* pClock) override; + HRESULT STDMETHODCALLTYPE GetSyncSource(IReferenceClock** pClock) override; + HRESULT STDMETHODCALLTYPE EnumPins(IEnumPins** ppEnum) override; + HRESULT STDMETHODCALLTYPE FindPin(LPCWSTR Id, IPin** ppPin) override; + HRESULT STDMETHODCALLTYPE QueryFilterInfo(FILTER_INFO* pInfo) override; + HRESULT STDMETHODCALLTYPE JoinFilterGraph(IFilterGraph* pGraph, LPCWSTR pName) override; + HRESULT STDMETHODCALLTYPE QueryVendorInfo(LPWSTR* pVendorInfo) override; + + HRESULT STDMETHODCALLTYPE GetClassID(CLSID* pClassID) override; + ULONG STDMETHODCALLTYPE GetMiscFlags(void) override; +}; +struct VideoCaptureProxyPin : winrt::implements +{ + VideoCaptureProxyFilter* _owningFilter = nullptr; + wil::com_ptr_nothrow _connectedInputPin; + unique_media_type_ptr _mediaFormat; + std::atomic_bool _flushing = false; + + HRESULT STDMETHODCALLTYPE Connect(IPin* pReceivePin, const AM_MEDIA_TYPE* pmt) override; + HRESULT STDMETHODCALLTYPE ReceiveConnection(IPin* pConnector, const AM_MEDIA_TYPE* pmt) override; + HRESULT STDMETHODCALLTYPE Disconnect(void) override; + HRESULT STDMETHODCALLTYPE ConnectedTo(IPin** pPin) override; + HRESULT STDMETHODCALLTYPE ConnectionMediaType(AM_MEDIA_TYPE* pmt) override; + HRESULT STDMETHODCALLTYPE QueryPinInfo(PIN_INFO* pInfo) override; + HRESULT STDMETHODCALLTYPE QueryDirection(PIN_DIRECTION* pPinDir) override; + HRESULT STDMETHODCALLTYPE QueryId(LPWSTR* Id) override; + HRESULT STDMETHODCALLTYPE QueryAccept(const AM_MEDIA_TYPE* pmt) override; + HRESULT STDMETHODCALLTYPE EnumMediaTypes(IEnumMediaTypes** ppEnum) override; + HRESULT STDMETHODCALLTYPE QueryInternalConnections(IPin** apPin, ULONG* nPin) override; + HRESULT STDMETHODCALLTYPE EndOfStream(void) override; + HRESULT STDMETHODCALLTYPE BeginFlush(void) override; + HRESULT STDMETHODCALLTYPE EndFlush(void) override; + HRESULT STDMETHODCALLTYPE NewSegment(REFERENCE_TIME tStart, REFERENCE_TIME tStop, double dRate) override; + + HRESULT STDMETHODCALLTYPE SetFormat(AM_MEDIA_TYPE* pmt) override; + HRESULT STDMETHODCALLTYPE GetFormat(AM_MEDIA_TYPE** ppmt) override; + HRESULT STDMETHODCALLTYPE GetNumberOfCapabilities(int* piCount, int* piSize) override; + HRESULT STDMETHODCALLTYPE GetStreamCaps(int iIndex, AM_MEDIA_TYPE** ppmt, BYTE* pSCC) override; + HRESULT STDMETHODCALLTYPE Set(REFGUID guidPropSet, + DWORD dwPropID, + LPVOID pInstanceData, + DWORD cbInstanceData, + LPVOID pPropData, + DWORD cbPropData) override; + HRESULT STDMETHODCALLTYPE Get(REFGUID guidPropSet, + DWORD dwPropID, + LPVOID pInstanceData, + DWORD cbInstanceData, + LPVOID pPropData, + DWORD cbPropData, + DWORD* pcbReturned) override; + HRESULT STDMETHODCALLTYPE QuerySupported(REFGUID guidPropSet, DWORD dwPropID, DWORD* pTypeSupport) override; + + wil::com_ptr_nothrow FindAllocator(); +}; diff --git a/src/modules/videoconference/VideoConferenceProxyFilter/VideoConferenceProxyFilter.vcxproj b/src/modules/videoconference/VideoConferenceProxyFilter/VideoConferenceProxyFilter.vcxproj new file mode 100644 index 0000000000..4bbb2c2282 --- /dev/null +++ b/src/modules/videoconference/VideoConferenceProxyFilter/VideoConferenceProxyFilter.vcxproj @@ -0,0 +1,124 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + 16.0 + {AC2857B4-103D-4D6D-9740-926EBF785042} + Win32Proj + VideoConferenceProxyFilter + VideoConferenceProxyFilter + true + 10.0.18362.0 + + + + DynamicLibrary + + + + $(SolutionDir)\src\;$(ProjectDir)..\..\..\ + Level4 + + + + + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + + + true + + + + + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + $(SolutionDir)\src\; + + + Console + + + true + + + call "$(ProjectDir)build_vcm_x86.cmd" + + + + $(SolutionDir)$(Platform)\$(Configuration)\modules\VideoConference\ + VideoConferenceProxyFilter_x64 + + + ..\..\..\..\x86\$(Configuration)\modules\VideoConference\ + VideoConferenceProxyFilter_x86 + + + + + + + + + + ..\VideoConferenceShared\;%(AdditionalIncludeDirectories) + _LIB;NOMINMAX;%(PreprocessorDefinitions) + NotUsing + MultiThreaded + MultiThreadedDebug + + + $(OutDir)VideoConferenceShared.lib;mfplat.lib;Mfsensorgroup.lib;OneCoreUAP.lib;Mf.lib;Shlwapi.lib;Strmiids.lib;%(AdditionalDependencies); + module.def + + + + + + + + + + + + + + + + + + + + {459e0768-7ebd-4c41-bba1-6db3b3815e0a} + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + \ No newline at end of file diff --git a/src/modules/videoconference/VideoConferenceProxyFilter/VideoConferenceProxyFilterx86.sln b/src/modules/videoconference/VideoConferenceProxyFilter/VideoConferenceProxyFilterx86.sln new file mode 100644 index 0000000000..e61fcf0bd6 --- /dev/null +++ b/src/modules/videoconference/VideoConferenceProxyFilter/VideoConferenceProxyFilterx86.sln @@ -0,0 +1,41 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30907.101 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "VideoConferenceProxyFilter", "VideoConferenceProxyFilter.vcxproj", "{AC2857B4-103D-4D6D-9740-926EBF785042}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "VideoConferenceShared", "..\VideoConferenceShared\VideoConferenceShared.vcxproj", "{459E0768-7EBD-4C41-BBA1-6DB3B3815E0A}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {AC2857B4-103D-4D6D-9740-926EBF785042}.Debug|Win32.ActiveCfg = Debug|Win32 + {AC2857B4-103D-4D6D-9740-926EBF785042}.Debug|Win32.Build.0 = Debug|Win32 + {AC2857B4-103D-4D6D-9740-926EBF785042}.Debug|x64.ActiveCfg = Debug|x64 + {AC2857B4-103D-4D6D-9740-926EBF785042}.Debug|x64.Build.0 = Debug|x64 + {AC2857B4-103D-4D6D-9740-926EBF785042}.Release|Win32.ActiveCfg = Release|Win32 + {AC2857B4-103D-4D6D-9740-926EBF785042}.Release|Win32.Build.0 = Release|Win32 + {AC2857B4-103D-4D6D-9740-926EBF785042}.Release|x64.ActiveCfg = Release|x64 + {AC2857B4-103D-4D6D-9740-926EBF785042}.Release|x64.Build.0 = Release|x64 + {459E0768-7EBD-4C41-BBA1-6DB3B3815E0A}.Debug|Win32.ActiveCfg = Debug|Win32 + {459E0768-7EBD-4C41-BBA1-6DB3B3815E0A}.Debug|Win32.Build.0 = Debug|Win32 + {459E0768-7EBD-4C41-BBA1-6DB3B3815E0A}.Debug|x64.ActiveCfg = Debug|x64 + {459E0768-7EBD-4C41-BBA1-6DB3B3815E0A}.Debug|x64.Build.0 = Debug|x64 + {459E0768-7EBD-4C41-BBA1-6DB3B3815E0A}.Release|Win32.ActiveCfg = Release|Win32 + {459E0768-7EBD-4C41-BBA1-6DB3B3815E0A}.Release|Win32.Build.0 = Release|Win32 + {459E0768-7EBD-4C41-BBA1-6DB3B3815E0A}.Release|x64.ActiveCfg = Release|x64 + {459E0768-7EBD-4C41-BBA1-6DB3B3815E0A}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {0E41348C-22CB-45A4-8A16-8D7BEA070BB2} + EndGlobalSection +EndGlobal diff --git a/src/modules/videoconference/VideoConferenceProxyFilter/build_vcm_x86.cmd b/src/modules/videoconference/VideoConferenceProxyFilter/build_vcm_x86.cmd new file mode 100644 index 0000000000..9b85078623 --- /dev/null +++ b/src/modules/videoconference/VideoConferenceProxyFilter/build_vcm_x86.cmd @@ -0,0 +1,3 @@ +msbuild VideoConferenceProxyFilterx86.sln -p:Configuration="Release" -p:Platform="Win32" + +exit 0 \ No newline at end of file diff --git a/src/modules/videoconference/VideoConferenceProxyFilter/dllmain.cpp b/src/modules/videoconference/VideoConferenceProxyFilter/dllmain.cpp new file mode 100644 index 0000000000..61133572c8 --- /dev/null +++ b/src/modules/videoconference/VideoConferenceProxyFilter/dllmain.cpp @@ -0,0 +1,242 @@ +#include "VideoCaptureProxyFilter.h" + +#include + +#include +#include + +namespace +{ +#if defined(_WIN64) + class __declspec(uuid("{31AD75E9-8C3A-49C8-B9ED-5880D6B4A764}")) GUID_DECL_POWERTOYS_VCM; +#elif defined(_WIN32) + class __declspec(uuid("{31AD75E9-8C3A-49C8-B9ED-5880D6B4A732}")) GUID_DECL_POWERTOYS_VCM; +#endif + const GUID CLSID_POWERTOYS_VCM = __uuidof(GUID_DECL_POWERTOYS_VCM); + + const REGPINTYPES MEDIA_TYPES = { &MEDIATYPE_Video, &MEDIASUBTYPE_MJPG }; + + const wchar_t FILTER_NAME[] = L"Output"; + const REGFILTERPINS PINS_REGISTRATION = { + (wchar_t*)FILTER_NAME, + false, + true, + false, + false, + &CLSID_NULL, + nullptr, + 1, + &MEDIA_TYPES + }; + + HINSTANCE DLLInstance{}; +} + +struct __declspec(uuid("9DCAF869-9C13-4BDF-BD0D-3592C5579DD6")) VideoCaptureProxyFilterFactory : winrt::implements +{ + HRESULT STDMETHODCALLTYPE CreateInstance(IUnknown*, REFIID riid, void** ppvObject) noexcept override + { + try + { + return winrt::make()->QueryInterface(riid, ppvObject); + } + catch (...) + { + return winrt::to_hresult(); + } + } + + HRESULT STDMETHODCALLTYPE LockServer(BOOL fLock) noexcept override + { + if (fLock) + { + ++winrt::get_module_lock(); + } + else + { + --winrt::get_module_lock(); + } + + return S_OK; + } +}; + +HRESULT STDMETHODCALLTYPE DllCanUnloadNow() +{ + if (winrt::get_module_lock()) + { + return S_FALSE; + } + + winrt::clear_factory_cache(); + return S_OK; +} + +HRESULT STDMETHODCALLTYPE DllGetClassObject(GUID const& clsid, GUID const& iid, void** result) +{ + if (!result) + { + return E_POINTER; + } + + if (iid != IID_IClassFactory && iid != IID_IUnknown) + { + return E_NOINTERFACE; + } + + if (clsid != CLSID_POWERTOYS_VCM) + { + return E_INVALIDARG; + } + + try + { + *result = nullptr; + + auto factory = winrt::make(); + factory->AddRef(); + *result = static_cast(factory.get()); + return S_OK; + } + catch (...) + { + return winrt::to_hresult(); + } +} + +std::wstring RegistryPath() +{ + std::wstring registryPath; + registryPath.resize(CHARS_IN_GUID, L'\0'); + + StringFromGUID2(CLSID_POWERTOYS_VCM, registryPath.data(), CHARS_IN_GUID); + registryPath.resize(registryPath.size() - 1); + registryPath = L"CLSID\\" + registryPath; + return registryPath; +} + +bool RegisterServer() +{ + std::wstring dllPath; + dllPath.resize(MAX_PATH, L'\0'); + if (auto length = GetModuleFileNameW(DLLInstance, dllPath.data(), MAX_PATH); length != 0) + { + dllPath.resize(length); + } + else + { + return false; + } + + wil::unique_hkey key; + wil::unique_hkey subkey; + const auto registryPath = RegistryPath(); + if (RegCreateKeyW(HKEY_CLASSES_ROOT, registryPath.c_str(), &key)) + { + return false; + } + + if (RegSetValueW(key.get(), nullptr, REG_SZ, CAMERA_NAME, sizeof(CAMERA_NAME))) + { + return false; + } + + if (RegCreateKeyW(key.get(), L"InprocServer32", &subkey)) + { + return false; + } + + if (RegSetValueW(subkey.get(), nullptr, REG_SZ, dllPath.c_str(), static_cast((dllPath.length() + 1) * sizeof(wchar_t)))) + { + return false; + } + const wchar_t THREADING_MODEL[] = L"Both"; + RegSetValueExW(subkey.get(), L"ThreadingModel", 0, REG_SZ, (const BYTE*)THREADING_MODEL, sizeof(THREADING_MODEL)); + + return true; +} + +bool UnregisterServer() +{ + const auto registryPath = RegistryPath(); + return !RegDeleteTreeW(HKEY_CLASSES_ROOT, registryPath.c_str()); +} + +bool RegisterFilter() +{ + auto filterMapper = wil::CoCreateInstanceNoThrow(CLSID_FilterMapper2); + if (!filterMapper) + { + return false; + } + + REGFILTER2 regFilter{ .dwVersion = 1, .dwMerit = MERIT_DO_NOT_USE, .cPins = 1, .rgPins = &PINS_REGISTRATION }; + + wil::com_ptr_nothrow moniker; + + return SUCCEEDED(filterMapper->RegisterFilter( + CLSID_POWERTOYS_VCM, CAMERA_NAME, &moniker, &CLSID_VideoInputDeviceCategory, nullptr, ®Filter)); +} + +bool UnregisterFilter() +{ + auto filterMapper = wil::CoCreateInstanceNoThrow(CLSID_FilterMapper2); + if (!filterMapper) + { + return false; + } + + return SUCCEEDED(filterMapper->UnregisterFilter(&CLSID_VideoInputDeviceCategory, nullptr, CLSID_POWERTOYS_VCM)); +} + +HRESULT STDMETHODCALLTYPE DllRegisterServer() +{ + if (!RegisterServer()) + { + UnregisterServer(); + return E_FAIL; + } + + auto COMContext = wil::CoInitializeEx(COINIT_APARTMENTTHREADED); + + if (!RegisterFilter()) + { + UnregisterFilter(); + UnregisterServer(); + return E_FAIL; + } + + return S_OK; +} + +HRESULT STDMETHODCALLTYPE DllUnregisterServer() +{ + auto COMContext = wil::CoInitializeEx(COINIT_APARTMENTTHREADED); + + UnregisterFilter(); + UnregisterServer(); + + return S_OK; +} + +HRESULT STDMETHODCALLTYPE DllInstall(BOOL install, LPCWSTR) +{ + if (install) + { + return DllRegisterServer(); + } + else + { + return DllUnregisterServer(); + } +} + +BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID) +{ + if (fdwReason == DLL_PROCESS_ATTACH) + { + DLLInstance = hinstDLL; + } + + return true; +} diff --git a/src/modules/videoconference/VideoConferenceProxyFilter/module.def b/src/modules/videoconference/VideoConferenceProxyFilter/module.def new file mode 100644 index 0000000000..c4fcbcc661 --- /dev/null +++ b/src/modules/videoconference/VideoConferenceProxyFilter/module.def @@ -0,0 +1,7 @@ +EXPORTS + DllMain PRIVATE + DllGetClassObject PRIVATE + DllCanUnloadNow PRIVATE + DllRegisterServer PRIVATE + DllUnregisterServer PRIVATE + DllInstall PRIVATE diff --git a/src/modules/videoconference/VideoConferenceProxyFilter/packages.config b/src/modules/videoconference/VideoConferenceProxyFilter/packages.config new file mode 100644 index 0000000000..bac888e145 --- /dev/null +++ b/src/modules/videoconference/VideoConferenceProxyFilter/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/modules/videoconference/VideoConferenceShared/CameraStateUpdateChannels.cpp b/src/modules/videoconference/VideoConferenceShared/CameraStateUpdateChannels.cpp new file mode 100644 index 0000000000..534428531b --- /dev/null +++ b/src/modules/videoconference/VideoConferenceShared/CameraStateUpdateChannels.cpp @@ -0,0 +1,15 @@ +#include "CameraStateUpdateChannels.h" + +#include "naming.h" + +std::wstring_view CameraOverlayImageChannel::endpoint() +{ + static const std::wstring endpoint = ObtainStableGlobalNameForKernelObject(L"PowerToysVideoConferenceCameraOverlayImageChannelSharedMemory", true); + return endpoint; +} + +std::wstring_view CameraSettingsUpdateChannel::endpoint() +{ + static const std::wstring endpoint = ObtainStableGlobalNameForKernelObject(L"PowerToysVideoConferenceSettingsChannelSharedMemory", true); + return endpoint; +} diff --git a/src/modules/videoconference/VideoConferenceShared/CameraStateUpdateChannels.h b/src/modules/videoconference/VideoConferenceShared/CameraStateUpdateChannels.h new file mode 100644 index 0000000000..02ec542f7a --- /dev/null +++ b/src/modules/videoconference/VideoConferenceShared/CameraStateUpdateChannels.h @@ -0,0 +1,23 @@ +#pragma once + +#include +#include +#include + +struct alignas(16) CameraSettingsUpdateChannel +{ + bool useOverlayImage = false; + bool cameraInUse = false; + + std::optional overlayImageSize; + std::optional> sourceCameraName; + + bool newOverlayImagePosted = false; + + static std::wstring_view endpoint(); +}; + +namespace CameraOverlayImageChannel +{ + std::wstring_view endpoint(); +} diff --git a/src/modules/videoconference/VideoConferenceShared/Logging.cpp b/src/modules/videoconference/VideoConferenceShared/Logging.cpp new file mode 100644 index 0000000000..382233f974 --- /dev/null +++ b/src/modules/videoconference/VideoConferenceShared/Logging.cpp @@ -0,0 +1,203 @@ +#include "Logging.h" + +#include +#include +#include +#include +#include +#include + +#include + +#pragma warning(disable : 4127) + +static std::mutex logMutex; +constexpr inline size_t maxLogSizeMegabytes = 10; +constexpr inline bool alwaysLogVerbose = true; + +void LogToFile(std::wstring what, const bool verbose) +{ + std::error_code _; + const auto tempPath = std::filesystem::temp_directory_path(_); + if (verbose) + { + const bool verboseIndicatorFilePresent = std::filesystem::exists(tempPath / L"PowerToysVideoConferenceVerbose.flag", _); + if (!alwaysLogVerbose && !verboseIndicatorFilePresent) + { + return; + } + } + time_t now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); + std::tm tm; + localtime_s(&tm, &now); + char prefix[64]; + const auto pid = GetCurrentProcessId(); + const auto iter = prefix + sprintf_s(prefix, "[%ld]", pid); + std::strftime(iter, sizeof(prefix) - (prefix - iter), "[%d.%m %H:%M:%S] ", &tm); + + std::lock_guard lock{ logMutex }; + std::wstring logFilePath = tempPath; +#if defined(_WIN64) + logFilePath += L"\\PowerToysVideoConference_x64.log"; +#elif defined(_WIN32) + logFilePath += L"\\PowerToysVideoConference_x86.log"; +#endif + size_t logSizeMBs = 0; + try + { + logSizeMBs = static_cast(std::filesystem::file_size(logFilePath) >> 20); + } + catch (...) + { + } + if (logSizeMBs > maxLogSizeMegabytes) + { + std::error_code __; + // Truncate the log file to zero + std::filesystem::resize_file(logFilePath, 0, __); + } + std::wofstream myfile; + myfile.open(logFilePath, std::fstream::app); + + static const auto newLaunch = [&] { + myfile << prefix << "\n\n<<>"; + return 0; + }(); + + myfile << prefix << what << "\n"; + myfile.close(); +} + +void LogToFile(std::string what, const bool verbose) +{ + std::wstring native{ begin(what), end(what) }; + LogToFile(std::move(native), verbose); +} + +std::string toMediaTypeString(GUID subtype) +{ + if (subtype == MFVideoFormat_YUY2) + return "MFVideoFormat_YUY2"; + else if (subtype == MFVideoFormat_RGB32) + return "MFVideoFormat_RGB32"; + else if (subtype == MFVideoFormat_RGB24) + return "MFVideoFormat_RGB24"; + else if (subtype == MFVideoFormat_ARGB32) + return "MFVideoFormat_ARGB32"; + else if (subtype == MFVideoFormat_RGB555) + return "MFVideoFormat_RGB555"; + else if (subtype == MFVideoFormat_RGB565) + return "MFVideoFormat_RGB565"; + else if (subtype == MFVideoFormat_RGB8) + return "MFVideoFormat_RGB8"; + else if (subtype == MFVideoFormat_L8) + return "MFVideoFormat_L8"; + else if (subtype == MFVideoFormat_L16) + return "MFVideoFormat_L16"; + else if (subtype == MFVideoFormat_D16) + return "MFVideoFormat_D16"; + else if (subtype == MFVideoFormat_AYUV) + return "MFVideoFormat_AYUV"; + else if (subtype == MFVideoFormat_YUY2) + return "MFVideoFormat_YUY2"; + else if (subtype == MFVideoFormat_YVYU) + return "MFVideoFormat_YVYU"; + else if (subtype == MFVideoFormat_YVU9) + return "MFVideoFormat_YVU9"; + else if (subtype == MFVideoFormat_UYVY) + return "MFVideoFormat_UYVY"; + else if (subtype == MFVideoFormat_NV11) + return "MFVideoFormat_NV11"; + else if (subtype == MFVideoFormat_NV12) + return "MFVideoFormat_NV12"; + else if (subtype == MFVideoFormat_YV12) + return "MFVideoFormat_YV12"; + else if (subtype == MFVideoFormat_I420) + return "MFVideoFormat_I420"; + else if (subtype == MFVideoFormat_IYUV) + return "MFVideoFormat_IYUV"; + else if (subtype == MFVideoFormat_Y210) + return "MFVideoFormat_Y210"; + else if (subtype == MFVideoFormat_Y216) + return "MFVideoFormat_Y216"; + else if (subtype == MFVideoFormat_Y410) + return "MFVideoFormat_Y410"; + else if (subtype == MFVideoFormat_Y416) + return "MFVideoFormat_Y416"; + else if (subtype == MFVideoFormat_Y41P) + return "MFVideoFormat_Y41P"; + else if (subtype == MFVideoFormat_Y41T) + return "MFVideoFormat_Y41T"; + else if (subtype == MFVideoFormat_Y42T) + return "MFVideoFormat_Y42T"; + else if (subtype == MFVideoFormat_P210) + return "MFVideoFormat_P210"; + else if (subtype == MFVideoFormat_P216) + return "MFVideoFormat_P216"; + else if (subtype == MFVideoFormat_P010) + return "MFVideoFormat_P010"; + else if (subtype == MFVideoFormat_P016) + return "MFVideoFormat_P016"; + else if (subtype == MFVideoFormat_v210) + return "MFVideoFormat_v210"; + else if (subtype == MFVideoFormat_v216) + return "MFVideoFormat_v216"; + else if (subtype == MFVideoFormat_v410) + return "MFVideoFormat_v410"; + else if (subtype == MFVideoFormat_MP43) + return "MFVideoFormat_MP43"; + else if (subtype == MFVideoFormat_MP4S) + return "MFVideoFormat_MP4S"; + else if (subtype == MFVideoFormat_M4S2) + return "MFVideoFormat_M4S2"; + else if (subtype == MFVideoFormat_MP4V) + return "MFVideoFormat_MP4V"; + else if (subtype == MFVideoFormat_WMV1) + return "MFVideoFormat_WMV1"; + else if (subtype == MFVideoFormat_WMV2) + return "MFVideoFormat_WMV2"; + else if (subtype == MFVideoFormat_WMV3) + return "MFVideoFormat_WMV3"; + else if (subtype == MFVideoFormat_WVC1) + return "MFVideoFormat_WVC1"; + else if (subtype == MFVideoFormat_MSS1) + return "MFVideoFormat_MSS1"; + else if (subtype == MFVideoFormat_MSS2) + return "MFVideoFormat_MSS2"; + else if (subtype == MFVideoFormat_MPG1) + return "MFVideoFormat_MPG1"; + else if (subtype == MFVideoFormat_DVSL) + return "MFVideoFormat_DVSL"; + else if (subtype == MFVideoFormat_DVSD) + return "MFVideoFormat_DVSD"; + else if (subtype == MFVideoFormat_DVHD) + return "MFVideoFormat_DVHD"; + else if (subtype == MFVideoFormat_DV25) + return "MFVideoFormat_DV25"; + else if (subtype == MFVideoFormat_DV50) + return "MFVideoFormat_DV50"; + else if (subtype == MFVideoFormat_DVH1) + return "MFVideoFormat_DVH1"; + else if (subtype == MFVideoFormat_DVC) + return "MFVideoFormat_DVC"; + else if (subtype == MFVideoFormat_H264) + return "MFVideoFormat_H264"; + else if (subtype == MFVideoFormat_H265) + return "MFVideoFormat_H265"; + else if (subtype == MFVideoFormat_MJPG) + return "MFVideoFormat_MJPG"; + else if (subtype == MFVideoFormat_420O) + return "MFVideoFormat_420O"; + else if (subtype == MFVideoFormat_HEVC) + return "MFVideoFormat_HEVC"; + else if (subtype == MFVideoFormat_HEVC_ES) + return "MFVideoFormat_HEVC_ES"; + else if (subtype == MFVideoFormat_VP80) + return "MFVideoFormat_VP80"; + else if (subtype == MFVideoFormat_VP90) + return "MFVideoFormat_VP90"; + else if (subtype == MFVideoFormat_ORAW) + return "MFVideoFormat_ORAW"; + else + return "Other VideoFormat"; +} \ No newline at end of file diff --git a/src/modules/videoconference/VideoConferenceShared/Logging.h b/src/modules/videoconference/VideoConferenceShared/Logging.h new file mode 100644 index 0000000000..633656f695 --- /dev/null +++ b/src/modules/videoconference/VideoConferenceShared/Logging.h @@ -0,0 +1,62 @@ +#pragma once + +#include +#include +#include + +#include +#include + +void LogToFile(std::string what, const bool verbose = false); +void LogToFile(std::wstring what, const bool verbose = false); +std::string toMediaTypeString(GUID subtype); + +#define RETURN_IF_FAILED_WITH_LOGGING(val) \ + hr = (val); \ + if (FAILED(hr)) \ + { \ + LogToFile(std::string(__FUNCTION__ "() ") + #val + ": " + std::system_category().message(hr)); \ + return hr; \ + } + +#define RETURN_NULLPTR_IF_FAILED_WITH_LOGGING(val) \ + hr = val; \ + if (FAILED(hr)) \ + { \ + LogToFile(std::string(__FUNCTION__ "() ") + #val + ": " + std::system_category().message(hr)); \ + return nullptr; \ + } + +#define VERBOSE_LOG \ + std::string functionNameTMPVAR = __FUNCTION__; \ + LogToFile(std::string(functionNameTMPVAR + " enter"), true); \ + auto verboseLogOnScopeEnd = wil::scope_exit([&] { \ + LogToFile(std::string(functionNameTMPVAR + " exit"), true); \ + }); + +#if defined(PowerToysInterop) +#undef LOG +#define LOG(...) +#else +#define LOG(str) LogToFile(str, false); +#endif + +inline bool failed(HRESULT hr) +{ + return hr != S_OK; +} + +inline bool failed(bool val) +{ + return val == false; +} + +template +inline bool failed(wil::com_ptr_nothrow& ptr) +{ + return ptr == nullptr; +} + +#define OK_OR_BAIL(expr) \ + if (failed(expr)) \ + return {}; diff --git a/src/modules/videoconference/VideoConferenceShared/MicrophoneDevice.cpp b/src/modules/videoconference/VideoConferenceShared/MicrophoneDevice.cpp new file mode 100644 index 0000000000..ad277ce041 --- /dev/null +++ b/src/modules/videoconference/VideoConferenceShared/MicrophoneDevice.cpp @@ -0,0 +1,152 @@ +#include "MicrophoneDevice.h" + +#include "Logging.h" + +#include + +MicrophoneDevice::MicrophoneDevice(wil::com_ptr_nothrow device, wil::com_ptr_nothrow endpoint) : + _device{ std::move(device) }, + _endpoint{ std::move(endpoint) } +{ + if (!_device || !_endpoint) + { + throw std::logic_error("MicrophoneDevice was initialized with null objects"); + } + _device->GetId(&_id); + wil::com_ptr_nothrow props; + _device->OpenPropertyStore( + STGM_READ, &props); + if (props) + { + props->GetValue(PKEY_Device_FriendlyName, &_friendly_name); + } + else + { + LOG("MicrophoneDevice::MicrophoneDevice couldn't open property store"); + } +} + +MicrophoneDevice::~MicrophoneDevice() +{ + if (_notifier) + { + _endpoint->UnregisterControlChangeNotify(_notifier.get()); + } +} + +bool MicrophoneDevice::active() const noexcept +{ + DWORD state = 0; + _device->GetState(&state); + return state == DEVICE_STATE_ACTIVE; +} + +void MicrophoneDevice::set_muted(const bool muted) noexcept +{ + _endpoint->SetMute(muted, nullptr); +} + +bool MicrophoneDevice::muted() const noexcept +{ + BOOL muted = FALSE; + _endpoint->GetMute(&muted); + return muted; +} + +void MicrophoneDevice::toggle_muted() noexcept +{ + set_muted(!muted()); +} + +std::wstring_view MicrophoneDevice::id() const noexcept +{ + return _id ? _id.get() : FALLBACK_ID; +} + +std::wstring_view MicrophoneDevice::name() const noexcept +{ + return _friendly_name.pwszVal ? _friendly_name.pwszVal : FALLBACK_NAME; +} + +void MicrophoneDevice::set_mute_changed_callback(mute_changed_cb_t callback) noexcept +{ + _mute_changed_callback = std::move(callback); + _notifier = winrt::make(this); + + _endpoint->RegisterControlChangeNotify(_notifier.get()); +} + +std::optional MicrophoneDevice::getDefault() +{ + auto deviceEnumerator = wil::CoCreateInstanceNoThrow(); + if (!deviceEnumerator) + { + LOG("MicrophoneDevice::getDefault MMDeviceEnumerator returned null"); + return std::nullopt; + } + wil::com_ptr_nothrow captureDevice; + deviceEnumerator->GetDefaultAudioEndpoint(eCapture, eCommunications, &captureDevice); + if (!captureDevice) + { + LOG("MicrophoneDevice::getDefault captureDevice is null"); + return std::nullopt; + } + wil::com_ptr_nothrow microphoneEndpoint; + captureDevice->Activate(__uuidof(IAudioEndpointVolume), CLSCTX_INPROC_SERVER, nullptr, reinterpret_cast(µphoneEndpoint)); + if (!microphoneEndpoint) + { + LOG("MicrophoneDevice::getDefault captureDevice is null"); + return std::nullopt; + } + return std::make_optional(std::move(captureDevice), std::move(microphoneEndpoint)); +} + +std::vector MicrophoneDevice::getAllActive() +{ + std::vector microphoneDevices; + auto deviceEnumerator = wil::CoCreateInstanceNoThrow(); + if (!deviceEnumerator) + { + LOG("MicrophoneDevice::getAllActive MMDeviceEnumerator returned null"); + return microphoneDevices; + } + + wil::com_ptr_nothrow captureDevices; + deviceEnumerator->EnumAudioEndpoints(eCapture, DEVICE_STATE_ACTIVE, &captureDevices); + if (!captureDevices) + { + LOG("MicrophoneDevice::getAllActive EnumAudioEndpoints returned null"); + return microphoneDevices; + } + UINT nDevices = 0; + captureDevices->GetCount(&nDevices); + microphoneDevices.reserve(nDevices); + for (UINT i = 0; i < nDevices; ++i) + { + wil::com_ptr_nothrow device; + captureDevices->Item(i, &device); + if (!device) + { + continue; + } + wil::com_ptr_nothrow microphoneEndpoint; + device->Activate(__uuidof(IAudioEndpointVolume), CLSCTX_INPROC_SERVER, nullptr, reinterpret_cast(µphoneEndpoint)); + if (!microphoneEndpoint) + { + continue; + } + microphoneDevices.emplace_back(std::move(device), std::move(microphoneEndpoint)); + } + return microphoneDevices; +} + +MicrophoneDevice::VolumeNotifier::VolumeNotifier(MicrophoneDevice* subscribedDevice) : + _subscribedDevice{ subscribedDevice } +{ +} + +HRESULT __stdcall MicrophoneDevice::VolumeNotifier::OnNotify(PAUDIO_VOLUME_NOTIFICATION_DATA data) +{ + _subscribedDevice->_mute_changed_callback(data->bMuted); + return S_OK; +} diff --git a/src/modules/videoconference/VideoConferenceShared/MicrophoneDevice.h b/src/modules/videoconference/VideoConferenceShared/MicrophoneDevice.h new file mode 100644 index 0000000000..8bcd096a68 --- /dev/null +++ b/src/modules/videoconference/VideoConferenceShared/MicrophoneDevice.h @@ -0,0 +1,65 @@ +#pragma once +#define NOMINMAX + +#include +#include + +#include +#include + +#include +#include + + +#include + +#include +#include +#include + +#include +#include + +class MicrophoneDevice +{ +public: + using mute_changed_cb_t = std::function; + +private: + friend struct VolumeNotifier; + + struct VolumeNotifier : winrt::implements + { + MicrophoneDevice* _subscribedDevice = nullptr; + VolumeNotifier(MicrophoneDevice* subscribedDevice); + + virtual HRESULT __stdcall OnNotify(PAUDIO_VOLUME_NOTIFICATION_DATA data) override; + }; + + wil::unique_cotaskmem_string _id; + wil::unique_prop_variant _friendly_name; + mute_changed_cb_t _mute_changed_callback; + winrt::com_ptr _notifier; + wil::com_ptr_nothrow _endpoint; + wil::com_ptr_nothrow _device; + + constexpr static inline std::wstring_view FALLBACK_NAME = L"Unknown device"; + constexpr static inline std::wstring_view FALLBACK_ID = L"UNKNOWN_ID"; + +public: + MicrophoneDevice(MicrophoneDevice&&) noexcept = default; + MicrophoneDevice(wil::com_ptr_nothrow device, wil::com_ptr_nothrow endpoint); + ~MicrophoneDevice(); + + bool active() const noexcept; + void set_muted(const bool muted) noexcept; + bool muted() const noexcept; + void toggle_muted() noexcept; + + std::wstring_view id() const noexcept; + std::wstring_view name() const noexcept; + void set_mute_changed_callback(mute_changed_cb_t callback) noexcept; + + static std::optional getDefault(); + static std::vector getAllActive(); +}; diff --git a/src/modules/videoconference/VideoConferenceShared/SerializedSharedMemory.cpp b/src/modules/videoconference/VideoConferenceShared/SerializedSharedMemory.cpp new file mode 100644 index 0000000000..541db26057 --- /dev/null +++ b/src/modules/videoconference/VideoConferenceShared/SerializedSharedMemory.cpp @@ -0,0 +1,188 @@ +#include "SerializedSharedMemory.h" + +inline char* SerializedSharedMemory::lock_flag_addr() noexcept +{ + return reinterpret_cast(_memory._data + _memory._size); +} + +inline void SerializedSharedMemory::lock() noexcept +{ + if (_read_only) + { + return; + } + while (LOCKED == _InterlockedCompareExchange8(lock_flag_addr(), LOCKED, !LOCKED)) + { + while (*lock_flag_addr() == LOCKED) + { + _mm_pause(); + } + } +} + +inline void SerializedSharedMemory::unlock() noexcept +{ + if (_read_only) + { + return; + } + _InterlockedExchange8(lock_flag_addr(), !LOCKED); +} + +SerializedSharedMemory::SerializedSharedMemory(std::array handles, + memory_t memory, + const bool readonly) noexcept + : + _handles{ std::move(handles) }, _memory{ std::move(memory) }, _read_only(readonly) +{ +} + +SerializedSharedMemory::~SerializedSharedMemory() noexcept +{ + if (_memory._data) + { + UnmapViewOfFile(_memory._data); + } +} + +SerializedSharedMemory::SerializedSharedMemory(SerializedSharedMemory&& rhs) noexcept +{ + *this = std::move(rhs); +} + +SerializedSharedMemory& SerializedSharedMemory::operator=(SerializedSharedMemory&& rhs) noexcept +{ + _handles = {}; + _handles.swap(rhs._handles); + _memory = std::move(rhs._memory); + rhs._memory = {}; + _read_only = rhs._read_only; + rhs._read_only = true; + + return *this; +} + +std::optional SerializedSharedMemory::create(const std::wstring_view object_name, + const size_t size, + const bool read_only, + SECURITY_ATTRIBUTES* maybe_attributes) noexcept +{ + SECURITY_DESCRIPTOR sd; + SECURITY_ATTRIBUTES sa = { sizeof SECURITY_ATTRIBUTES }; + if (!maybe_attributes) + { + sa.lpSecurityDescriptor = &sd; + sa.bInheritHandle = false; + if (!InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION) || + !SetSecurityDescriptorDacl(&sd, true, nullptr, false)) + { + return std::nullopt; + } + } + + // We need an extra byte for locking if it's not readonly + const ULARGE_INTEGER UISize{ .QuadPart = size + !read_only }; + + wil::unique_handle hMapFile{ CreateFileMappingW(INVALID_HANDLE_VALUE, + maybe_attributes ? maybe_attributes : &sa, + read_only ? PAGE_READONLY : PAGE_READWRITE, + UISize.HighPart, + UISize.LowPart, + object_name.data()) }; + if (!hMapFile) + { + return std::nullopt; + } + auto shmem = static_cast( + MapViewOfFile(hMapFile.get(), read_only ? FILE_MAP_READ : FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, static_cast(UISize.QuadPart))); + if (!shmem) + { + return std::nullopt; + } + std::array handles = { std::move(hMapFile), {} }; + return SerializedSharedMemory{ std::move(handles), memory_t{ shmem, size }, read_only }; +} + +std::optional SerializedSharedMemory::open(const std::wstring_view object_name, + const size_t size, + const bool read_only) noexcept +{ + wil::unique_handle hMapFile{ OpenFileMappingW(FILE_MAP_READ | FILE_MAP_WRITE, FALSE, object_name.data()) }; + if (!hMapFile) + { + return std::nullopt; + } + + auto shmem = static_cast( + MapViewOfFile(hMapFile.get(), read_only ? FILE_MAP_READ : FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, size + !read_only)); + + if (!shmem) + { + return std::nullopt; + } + std::array handles = { std::move(hMapFile), {} }; + return SerializedSharedMemory{ std::move(handles), memory_t{ shmem, size }, read_only }; +} + +std::optional SerializedSharedMemory::create_readonly( + const std::wstring_view object_name, + const std::wstring_view file_path, + SECURITY_ATTRIBUTES* maybe_attributes) noexcept +{ + SECURITY_DESCRIPTOR sd; + SECURITY_ATTRIBUTES sa = { sizeof SECURITY_ATTRIBUTES }; + if (!maybe_attributes) + { + sa.lpSecurityDescriptor = &sd; + sa.bInheritHandle = false; + if (!InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION) || + !SetSecurityDescriptorDacl(&sd, true, nullptr, false)) + { + return std::nullopt; + } + } + wil::unique_handle hFile{ CreateFileW(file_path.data(), + GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE, + maybe_attributes ? maybe_attributes : &sa, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + nullptr) }; + + if (!hFile) + { + return std::nullopt; + } + + LARGE_INTEGER fileSize; + if (!GetFileSizeEx(hFile.get(), &fileSize)) + { + return std::nullopt; + } + wil::unique_handle hMapFile{ CreateFileMappingW(hFile.get(), + maybe_attributes ? maybe_attributes : &sa, + PAGE_READONLY, + fileSize.HighPart, + fileSize.LowPart, + object_name.data()) }; + if (!hMapFile) + { + return std::nullopt; + } + + auto shmem = static_cast(MapViewOfFile(nullptr, FILE_MAP_READ, 0, 0, static_cast(fileSize.QuadPart))); + if (shmem) + { + return std::nullopt; + } + std::array handles = { std::move(hMapFile), std::move(hFile) }; + + return SerializedSharedMemory{ std::move(handles), memory_t{ shmem, static_cast(fileSize.QuadPart) }, true }; +} + +void SerializedSharedMemory::access(std::function access_routine) noexcept +{ + lock(); + access_routine(_memory); + unlock(); +} \ No newline at end of file diff --git a/src/modules/videoconference/VideoConferenceShared/SerializedSharedMemory.h b/src/modules/videoconference/VideoConferenceShared/SerializedSharedMemory.h new file mode 100644 index 0000000000..15f1052746 --- /dev/null +++ b/src/modules/videoconference/VideoConferenceShared/SerializedSharedMemory.h @@ -0,0 +1,54 @@ +#pragma once +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif + +#include +#include +#include +#include +#include +#include + +// Wrapper class allowing sharing readonly/writable memory with a serialized access via atomic locking. +// Note that it doesn't protect against a 3rd party concurrently modifying physical file contents. +class SerializedSharedMemory +{ +public: + struct memory_t + { + uint8_t * _data = nullptr; + size_t _size = 0; + }; + + static std::optional create(const std::wstring_view object_name, + const size_t size, + const bool read_only, + SECURITY_ATTRIBUTES* maybe_attributes = nullptr) noexcept; + static std::optional create_readonly( + const std::wstring_view object_name, + const std::wstring_view file_path, + SECURITY_ATTRIBUTES* maybe_attributes = nullptr) noexcept; + static std::optional open(const std::wstring_view object_name, + const size_t size, + const bool read_only) noexcept; + + void access(std::function access_routine) noexcept; + inline size_t size() const noexcept { return _memory._size; } + + ~SerializedSharedMemory() noexcept; + SerializedSharedMemory(SerializedSharedMemory&&) noexcept; + SerializedSharedMemory& operator=(SerializedSharedMemory&&) noexcept; + +private: + std::array _handles; + memory_t _memory; + bool _read_only = true; + constexpr static inline int64_t LOCKED = 1; + + char* lock_flag_addr() noexcept; + void lock() noexcept; + void unlock() noexcept; + + SerializedSharedMemory(std::array handles, memory_t memory, const bool readonly) noexcept; +}; \ No newline at end of file diff --git a/src/modules/videoconference/VideoConferenceShared/VideoCaptureDeviceList.cpp b/src/modules/videoconference/VideoConferenceShared/VideoCaptureDeviceList.cpp new file mode 100644 index 0000000000..3245524aad --- /dev/null +++ b/src/modules/videoconference/VideoConferenceShared/VideoCaptureDeviceList.cpp @@ -0,0 +1,100 @@ +#include "VideoCaptureDeviceList.h" +#include "Logging.h" +#include +#include + +#include +#include + +void VideoCaptureDeviceList::Clear() +{ + for (UINT32 i = 0; i < m_numberDevices; i++) + { + CoTaskMemFree(m_deviceFriendlyNames[i]); + if (m_ppDevices[i]) + { + m_ppDevices[i]->Release(); + } + } + CoTaskMemFree(m_ppDevices); + m_ppDevices = nullptr; + if (m_deviceFriendlyNames) + { + delete[] m_deviceFriendlyNames; + } + + m_deviceFriendlyNames = nullptr; + m_numberDevices = 0; +} + +HRESULT VideoCaptureDeviceList::EnumerateDevices() +{ + HRESULT hr = S_OK; + wil::com_ptr pAttributes; + Clear(); + + // Initialize an attribute store. We will use this to + // specify the enumeration parameters. + + hr = MFCreateAttributes(&pAttributes, 1); + + // Ask for source type = video capture devices + if (SUCCEEDED(hr)) + { + hr = pAttributes->SetGUID( + MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, + MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID); + } + else + { + LOG("VideoCaptureDeviceList::EnumerateDevices(): Couldn't MFCreateAttributes"); + } + // Enumerate devices. + if (SUCCEEDED(hr)) + { + hr = MFEnumDeviceSources(pAttributes.get(), &m_ppDevices, &m_numberDevices); + } + else + { + LOG("VideoCaptureDeviceList::EnumerateDevices(): Couldn't SetGUID"); + } + + + if (FAILED(hr)) + { + LOG("VideoCaptureDeviceList::EnumerateDevices(): MFEnumDeviceSources failed"); + return hr; + } + + m_deviceFriendlyNames = new (std::nothrow) wchar_t*[m_numberDevices]; + for (UINT32 i = 0; i < m_numberDevices; i++) + { + UINT32 nameLength = 0; + m_ppDevices[i]->GetAllocatedString(MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME, &m_deviceFriendlyNames[i], &nameLength); + } + + return hr; +} + +HRESULT VideoCaptureDeviceList::GetDevice(UINT32 index, IMFActivate** ppActivate) +{ + if (index >= Count()) + { + return E_INVALIDARG; + } + + *ppActivate = m_ppDevices[index]; + (*ppActivate)->AddRef(); + + return S_OK; +} + +std::wstring_view VideoCaptureDeviceList::GetDeviceName(UINT32 index) +{ + if (index >= Count()) + { + return {}; + } + + return m_deviceFriendlyNames[index]; +} diff --git a/src/modules/videoconference/VideoConferenceShared/VideoCaptureDeviceList.h b/src/modules/videoconference/VideoConferenceShared/VideoCaptureDeviceList.h new file mode 100644 index 0000000000..eb0bf4afd4 --- /dev/null +++ b/src/modules/videoconference/VideoConferenceShared/VideoCaptureDeviceList.h @@ -0,0 +1,33 @@ +#pragma once + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include +#include +#include + +class VideoCaptureDeviceList +{ + UINT32 m_numberDevices; + // TODO: use wil + IMFActivate** m_ppDevices = nullptr; + wchar_t** m_deviceFriendlyNames = nullptr; + +public: + VideoCaptureDeviceList() : + m_ppDevices(NULL), m_numberDevices(0) + { + } + ~VideoCaptureDeviceList() + { + Clear(); + } + + UINT32 Count() const { return m_numberDevices; } + + void Clear(); + HRESULT EnumerateDevices(); + HRESULT GetDevice(UINT32 index, IMFActivate** ppActivate); + std::wstring_view GetDeviceName(UINT32 index); +}; diff --git a/src/modules/videoconference/VideoConferenceShared/VideoConferenceShared.vcxproj b/src/modules/videoconference/VideoConferenceShared/VideoConferenceShared.vcxproj new file mode 100644 index 0000000000..1b628c7e89 --- /dev/null +++ b/src/modules/videoconference/VideoConferenceShared/VideoConferenceShared.vcxproj @@ -0,0 +1,140 @@ + + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + + mfplat.lib;Mfsensorgroup.lib;OneCoreUAP.lib;Mf.lib;Shlwapi.lib;Strmiids.lib;%(AdditionalDependencies); + + + + 16.0 + Win32Proj + {459e0768-7ebd-4c41-bba1-6db3b3815e0a} + VideoConferenceShared + true + 10.0.18362.0 + + + + StaticLibrary + true + v142 + Unicode + + + + + + + + + + + + $(SolutionDir)$(Platform)\$(Configuration)\modules\VideoConference\ + $(SolutionDir)$(Platform)\$(Configuration)\obj\$(ProjectName)\ + + + ..\..\..\..\x86\$(Configuration)\modules\VideoConference\ + ..\..\..\..\x86\$(Configuration)\obj\$(ProjectName)\ + + + true + + + + NotUsing + + + + + Level4 + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + MultiThreadedDebug + true + $(SolutionDir)\src\; + + + Console + true + + + true + + + + + Level4 + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + MultiThreaded + true + $(SolutionDir)\src\; + ProgramDatabase + + + Console + true + true + true + + + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + \ No newline at end of file diff --git a/src/modules/videoconference/VideoConferenceShared/naming.cpp b/src/modules/videoconference/VideoConferenceShared/naming.cpp new file mode 100644 index 0000000000..b93088cd00 --- /dev/null +++ b/src/modules/videoconference/VideoConferenceShared/naming.cpp @@ -0,0 +1,19 @@ +#include "naming.h" + +#include "username.h" + +std::wstring ObtainStableGlobalNameForKernelObject(const std::wstring_view name, const bool restricted) +{ + static const std::optional username = ObtainActiveUserName(); + std::wstring result = L"Global\\"; + if (restricted) + { + result += L"Restricted\\"; + } + if (username) + { + result += *username; + } + result += name; + return result; +} diff --git a/src/modules/videoconference/VideoConferenceShared/naming.h b/src/modules/videoconference/VideoConferenceShared/naming.h new file mode 100644 index 0000000000..8d127b0623 --- /dev/null +++ b/src/modules/videoconference/VideoConferenceShared/naming.h @@ -0,0 +1,5 @@ +#pragma once +#include +#include + +std::wstring ObtainStableGlobalNameForKernelObject(const std::wstring_view name, const bool restricted); \ No newline at end of file diff --git a/src/modules/videoconference/VideoConferenceShared/packages.config b/src/modules/videoconference/VideoConferenceShared/packages.config new file mode 100644 index 0000000000..eea1154c92 --- /dev/null +++ b/src/modules/videoconference/VideoConferenceShared/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/modules/videoconference/VideoConferenceShared/username.cpp b/src/modules/videoconference/VideoConferenceShared/username.cpp new file mode 100644 index 0000000000..d8b99e8dd5 --- /dev/null +++ b/src/modules/videoconference/VideoConferenceShared/username.cpp @@ -0,0 +1,20 @@ +#include "username.h" + +#include +#include + +std::optional ObtainActiveUserName() +{ + const DWORD sessionId = WTSGetActiveConsoleSessionId(); + WCHAR* pUserName; + DWORD _ = 0; + + if (!WTSQuerySessionInformationW(WTS_CURRENT_SERVER_HANDLE, sessionId, WTSUserName, &pUserName, &_)) + { + return std::nullopt; + } + WTSGetActiveConsoleSessionId(); + std::wstring result{ pUserName }; + WTSFreeMemory(pUserName); + return result; +} diff --git a/src/modules/videoconference/VideoConferenceShared/username.h b/src/modules/videoconference/VideoConferenceShared/username.h new file mode 100644 index 0000000000..dcbefec38b --- /dev/null +++ b/src/modules/videoconference/VideoConferenceShared/username.h @@ -0,0 +1,9 @@ +#pragma once + +#include +#include +#include + +std::optional ObtainActiveUserName(); + +std::wstring ObtainStableGlobalNameForKernelObject(const std::wstring_view name, const bool restricted); \ No newline at end of file diff --git a/src/modules/videoconference/make_cab.ddf b/src/modules/videoconference/make_cab.ddf new file mode 100644 index 0000000000..0c75024114 --- /dev/null +++ b/src/modules/videoconference/make_cab.ddf @@ -0,0 +1,20 @@ +; Disable default limits +.option EXPLICIT +.set CabinetFileCountThreshold=0 +.set FolderFileCountThreshold=0 +.set FolderSizeThreshold=0 +.set MaxCabinetSize=0 +.set MaxDiskFileCount=0 +.set MaxDiskSize=0 + +.set GenerateInf=ON +.set Compress=OFF +.set Cabinet=ON +.set CabinetNameTemplate=driver.cab +.set DestinationDir=cab_output +.set DiskDirectoryTemplate=driver + +VideoConferenceCustomMediaSource.dll +videoconferencevirtualdriver.cat +VideoConferenceVirtualDriver.dll +VideoConferenceVirtualDriver.inf \ No newline at end of file diff --git a/src/runner/main.cpp b/src/runner/main.cpp index 2f1b07f29b..a1e9a4c5fd 100644 --- a/src/runner/main.cpp +++ b/src/runner/main.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include @@ -43,6 +44,7 @@ #include #include #include +#include namespace { @@ -130,7 +132,7 @@ int runner(bool isProcessElevated, bool openSettings, bool openOobe) chdir_current_executable(); // Load Powertoys DLLs - const std::array knownModules = { + std::vector knownModules = { L"modules/FancyZones/FancyZonesModuleInterface.dll", L"modules/FileExplorerPreview/powerpreview.dll", L"modules/ImageResizer/ImageResizerExt.dll", @@ -140,6 +142,8 @@ int runner(bool isProcessElevated, bool openSettings, bool openOobe) L"modules/ShortcutGuide/ShortcutGuideModuleInterface/ShortcutGuideModuleInterface.dll", L"modules/ColorPicker/ColorPicker.dll", L"modules/Awake/AwakeModuleInterface.dll", + // TODO(yuyoyuppe): uncomment when VCM should be enabled + //L"modules/VideoConference/VideoConferenceModule.dll" }; for (const auto& moduleSubdir : knownModules) @@ -264,6 +268,10 @@ toast_notification_handler_result toast_notification_handler(const std::wstring_ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { + Gdiplus::GdiplusStartupInput gpStartupInput; + ULONG_PTR gpToken; + GdiplusStartup(&gpToken, &gpStartupInput, NULL); + winrt::init_apartment(); const wchar_t* securityDescriptor = L"O:BA" // Owner: Builtin (local) administrator diff --git a/src/runner/runner.vcxproj b/src/runner/runner.vcxproj index 7c12539128..4311f3beab 100644 --- a/src/runner/runner.vcxproj +++ b/src/runner/runner.vcxproj @@ -38,7 +38,7 @@ AsInvoker $(OutDir)$(TargetName)$(TargetExt) - Shcore.lib;Msi.lib;WindowsApp.lib;taskschd.lib;Rstrtmgr.lib;Shlwapi.lib;%(AdditionalDependencies) + Shcore.lib;gdiplus.lib;Msi.lib;WindowsApp.lib;taskschd.lib;Rstrtmgr.lib;Shlwapi.lib;%(AdditionalDependencies) false diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/EnabledModules.cs b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/EnabledModules.cs index 05039c6392..227ebb9488 100644 --- a/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/EnabledModules.cs +++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/EnabledModules.cs @@ -80,6 +80,22 @@ namespace Microsoft.PowerToys.Settings.UI.Library } } + private bool videoConference = true; + + [JsonPropertyName("Video Conference")] + public bool VideoConference + { + get => this.videoConference; + set + { + if (this.videoConference != value) + { + LogTelemetryEvent(value); + this.videoConference = value; + } + } + } + private bool powerRename = true; public bool PowerRename diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/Microsoft.PowerToys.Settings.UI.Library.csproj b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/Microsoft.PowerToys.Settings.UI.Library.csproj index e039aa704e..2b4e5447b9 100644 --- a/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/Microsoft.PowerToys.Settings.UI.Library.csproj +++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/Microsoft.PowerToys.Settings.UI.Library.csproj @@ -58,4 +58,11 @@ + + + + C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.7.2\System.Windows.Forms.dll + + + diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/SndVideoConferenceSettings.cs b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/SndVideoConferenceSettings.cs new file mode 100644 index 0000000000..46e7a420a0 --- /dev/null +++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/SndVideoConferenceSettings.cs @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Text; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Microsoft.PowerToys.Settings.UI.Library +{ + public class SndVideoConferenceSettings + { + [JsonPropertyName("Video Conference")] + public VideoConferenceSettings VideoConference { get; set; } + + public SndVideoConferenceSettings(VideoConferenceSettings settings) + { + VideoConference = settings; + } + + public string ToJsonString() + { + return JsonSerializer.Serialize(this); + } + } +} diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/StringProperty.cs b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/StringProperty.cs index c138424afb..4e632f79b6 100644 --- a/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/StringProperty.cs +++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/StringProperty.cs @@ -2,6 +2,7 @@ // The Microsoft Corporation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Text.Json; using System.Text.Json.Serialization; @@ -29,5 +30,15 @@ namespace Microsoft.PowerToys.Settings.UI.Library { return JsonSerializer.Serialize(this); } + + public static StringProperty ToStringProperty(string v) + { + return new StringProperty(v); + } + + public static implicit operator StringProperty(string v) + { + return new StringProperty(v); + } } } diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/VideoConferenceConfigProperties.cs b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/VideoConferenceConfigProperties.cs new file mode 100644 index 0000000000..d72e49aa0b --- /dev/null +++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/VideoConferenceConfigProperties.cs @@ -0,0 +1,86 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Microsoft.PowerToys.Settings.UI.Library +{ + public class VideoConferenceConfigProperties + { + public VideoConferenceConfigProperties() + { + this.MuteCameraAndMicrophoneHotkey = new KeyboardKeysProperty( + new HotkeySettings() + { + Win = true, + Ctrl = false, + Alt = false, + Shift = false, + Key = "N", + Code = 78, + }); + + this.MuteMicrophoneHotkey = new KeyboardKeysProperty( + new HotkeySettings() + { + Win = true, + Ctrl = false, + Alt = false, + Shift = true, + Key = "A", + Code = 65, + }); + + this.MuteCameraHotkey = new KeyboardKeysProperty( + new HotkeySettings() + { + Win = true, + Ctrl = false, + Alt = false, + Shift = true, + Key = "O", + Code = 79, + }); + + this.HideToolbarWhenUnmuted = new BoolProperty(true); + } + + [JsonPropertyName("mute_camera_and_microphone_hotkey")] + public KeyboardKeysProperty MuteCameraAndMicrophoneHotkey { get; set; } + + [JsonPropertyName("mute_microphone_hotkey")] + public KeyboardKeysProperty MuteMicrophoneHotkey { get; set; } + + [JsonPropertyName("mute_camera_hotkey")] + public KeyboardKeysProperty MuteCameraHotkey { get; set; } + + [JsonPropertyName("selected_camera")] + public StringProperty SelectedCamera { get; set; } = string.Empty; + + [JsonPropertyName("selected_mic")] + public StringProperty SelectedMicrophone { get; set; } = string.Empty; + + [JsonPropertyName("toolbar_position")] + public StringProperty ToolbarPosition { get; set; } = "Top right corner"; + + [JsonPropertyName("toolbar_monitor")] + public StringProperty ToolbarMonitor { get; set; } = "Main monitor"; + + [JsonPropertyName("camera_overlay_image_path")] + public StringProperty CameraOverlayImagePath { get; set; } = string.Empty; + + [JsonPropertyName("theme")] + public StringProperty Theme { get; set; } + + [JsonPropertyName("hide_toolbar_when_unmuted")] + public BoolProperty HideToolbarWhenUnmuted { get; set; } + + // converts the current to a json string. + public string ToJsonString() + { + return JsonSerializer.Serialize(this); + } + } +} diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/VideoConferenceSettings.cs b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/VideoConferenceSettings.cs new file mode 100644 index 0000000000..b206dc7fb7 --- /dev/null +++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/VideoConferenceSettings.cs @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Text.Json; +using System.Text.Json.Serialization; +using Microsoft.PowerToys.Settings.UI.Library.Interfaces; + +namespace Microsoft.PowerToys.Settings.UI.Library +{ + public class VideoConferenceSettings : BasePTModuleSettings, ISettingsConfig + { + public VideoConferenceSettings() + { + Version = "1"; + Name = "Video Conference"; + Properties = new VideoConferenceConfigProperties(); + } + + [JsonPropertyName("properties")] + public VideoConferenceConfigProperties Properties { get; set; } + + public string GetModuleName() + { + return Name; + } + + public bool UpgradeSettingsConfiguration() + { + return false; + } + } +} diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/VideoConferenceSettingsIPCMessage.cs b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/VideoConferenceSettingsIPCMessage.cs new file mode 100644 index 0000000000..1fdc7f489e --- /dev/null +++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/VideoConferenceSettingsIPCMessage.cs @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Microsoft.PowerToys.Settings.UI.Library +{ + public class VideoConferenceSettingsIPCMessage + { + [JsonPropertyName("powertoys")] + public SndVideoConferenceSettings Powertoys { get; set; } + + public VideoConferenceSettingsIPCMessage() + { + } + + public VideoConferenceSettingsIPCMessage(SndVideoConferenceSettings settings) + { + this.Powertoys = settings; + } + + public string ToJsonString() + { + return JsonSerializer.Serialize(this); + } + } +} diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/ViewModels/VideoConferenceViewModel.cs b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/ViewModels/VideoConferenceViewModel.cs new file mode 100644 index 0000000000..03494cc2f8 --- /dev/null +++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/ViewModels/VideoConferenceViewModel.cs @@ -0,0 +1,432 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Linq; + +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Windows.Forms; +using Microsoft.PowerToys.Settings.UI.Library; +using Microsoft.PowerToys.Settings.UI.Library.Helpers; +using Microsoft.PowerToys.Settings.UI.Library.Interfaces; +using Microsoft.PowerToys.Settings.UI.Library.ViewModels.Commands; + +namespace Microsoft.PowerToys.Settings.UI.ViewModels +{ + public class VideoConferenceViewModel : Observable + { + private readonly ISettingsUtils _settingsUtils; + + private VideoConferenceSettings Settings { get; set; } + + private GeneralSettings GeneralSettingsConfig { get; set; } + + private const string ModuleName = "Video Conference"; + + private Func SendConfigMSG { get; } + + private string _settingsConfigFileFolder = string.Empty; + + public VideoConferenceViewModel(ISettingsUtils settingsUtils, ISettingsRepository settingsRepository, Func ipcMSGCallBackFunc, string configFileSubfolder = "") + { + if (settingsRepository == null) + { + throw new ArgumentNullException(nameof(settingsRepository)); + } + + GeneralSettingsConfig = settingsRepository.SettingsConfig; + + _settingsUtils = settingsUtils ?? throw new ArgumentNullException(nameof(settingsUtils)); + + SendConfigMSG = ipcMSGCallBackFunc; + + _settingsConfigFileFolder = configFileSubfolder; + + try + { + Settings = _settingsUtils.GetSettings(GetSettingsSubPath()); + } +#pragma warning disable CA1031 // Do not catch general exception types + catch +#pragma warning restore CA1031 // Do not catch general exception types + { + Settings = new VideoConferenceSettings(); + _settingsUtils.SaveSettings(Settings.ToJsonString(), GetSettingsSubPath()); + } + + CameraNames = interop.CommonManaged.GetAllVideoCaptureDeviceNames(); + MicrophoneNames = interop.CommonManaged.GetAllActiveMicrophoneDeviceNames(); + MicrophoneNames.Insert(0, "[All]"); + + var shouldSaveSettings = false; + + if (string.IsNullOrEmpty(Settings.Properties.SelectedCamera.Value) && CameraNames.Count != 0) + { + _selectedCameraIndex = 0; + Settings.Properties.SelectedCamera.Value = CameraNames[0]; + shouldSaveSettings = true; + } + else + { + _selectedCameraIndex = CameraNames.FindIndex(name => name == Settings.Properties.SelectedCamera.Value); + } + + if (string.IsNullOrEmpty(Settings.Properties.SelectedMicrophone.Value)) + { + _selectedMicrophoneIndex = 0; + Settings.Properties.SelectedMicrophone.Value = MicrophoneNames[0]; + shouldSaveSettings = true; + } + else + { + _selectedMicrophoneIndex = MicrophoneNames.FindIndex(name => name == Settings.Properties.SelectedMicrophone.Value); + } + + _isEnabled = GeneralSettingsConfig.Enabled.VideoConference; + _cameraAndMicrophoneMuteHotkey = Settings.Properties.MuteCameraAndMicrophoneHotkey.Value; + _mirophoneMuteHotkey = Settings.Properties.MuteMicrophoneHotkey.Value; + _cameraMuteHotkey = Settings.Properties.MuteCameraHotkey.Value; + CameraImageOverlayPath = Settings.Properties.CameraOverlayImagePath.Value; + SelectOverlayImage = new ButtonClickCommand(SelectOverlayImageAction); + ClearOverlayImage = new ButtonClickCommand(ClearOverlayImageAction); + + _hideToolbarWhenUnmuted = Settings.Properties.HideToolbarWhenUnmuted.Value; + + switch (Settings.Properties.ToolbarPosition.Value) + { + case "Top left corner": + _toolbarPositionIndex = 0; + break; + case "Top center": + _toolbarPositionIndex = 1; + break; + case "Top right corner": + _toolbarPositionIndex = 2; + break; + case "Bottom left corner": + _toolbarPositionIndex = 3; + break; + case "Bottom center": + _toolbarPositionIndex = 4; + break; + case "Bottom right corner": + _toolbarPositionIndex = 5; + break; + } + + switch (Settings.Properties.ToolbarMonitor.Value) + { + case "Main monitor": + _toolbarMonitorIndex = 0; + break; + + case "All monitors": + _toolbarMonitorIndex = 1; + break; + } + + if (shouldSaveSettings) + { + _settingsUtils.SaveSettings(Settings.ToJsonString(), ModuleName); + } + } + + private bool _isEnabled; + private int _toolbarPositionIndex; + private int _toolbarMonitorIndex; + private HotkeySettings _cameraAndMicrophoneMuteHotkey; + private HotkeySettings _mirophoneMuteHotkey; + private HotkeySettings _cameraMuteHotkey; + private int _selectedCameraIndex = -1; + private int _selectedMicrophoneIndex; + private bool _hideToolbarWhenUnmuted; + + public List CameraNames { get; } + + public List MicrophoneNames { get; } + + public string CameraImageOverlayPath { get; set; } + + public ButtonClickCommand SelectOverlayImage { get; set; } + + public ButtonClickCommand ClearOverlayImage { get; set; } + + private void ClearOverlayImageAction() + { + CameraImageOverlayPath = string.Empty; + Settings.Properties.CameraOverlayImagePath = string.Empty; + RaisePropertyChanged(nameof(CameraImageOverlayPath)); + } + + private void SelectOverlayImageAction() + { + try + { + string pickedImage = null; + using (OpenFileDialog openFileDialog = new OpenFileDialog()) + { + openFileDialog.Filter = "Image Files (*.jpeg;*.jpg;*.png)|*.jpeg;*.jpg;*.png"; + openFileDialog.RestoreDirectory = true; + if (openFileDialog.ShowDialog() == DialogResult.OK) + { + pickedImage = openFileDialog.FileName; + } + } + + if (pickedImage != null) + { + CameraImageOverlayPath = pickedImage; + Settings.Properties.CameraOverlayImagePath = pickedImage; + RaisePropertyChanged(nameof(CameraImageOverlayPath)); + } + } +#pragma warning disable CA1031 // Do not catch general exception types + catch +#pragma warning restore CA1031 // Do not catch general exception types + { + } + } + + public int SelectedCameraIndex + { + get + { + return _selectedCameraIndex; + } + + set + { + if (_selectedCameraIndex != value) + { + _selectedCameraIndex = value; + if (_selectedCameraIndex >= 0 && _selectedCameraIndex < CameraNames.Count) + { + Settings.Properties.SelectedCamera.Value = CameraNames[_selectedCameraIndex]; + RaisePropertyChanged(); + } + } + } + } + + public int SelectedMicrophoneIndex + { + get + { + return _selectedMicrophoneIndex; + } + + set + { + if (_selectedMicrophoneIndex != value) + { + _selectedMicrophoneIndex = value; + if (_selectedMicrophoneIndex >= 0 && _selectedMicrophoneIndex < MicrophoneNames.Count) + { + Settings.Properties.SelectedMicrophone.Value = MicrophoneNames[_selectedMicrophoneIndex]; + RaisePropertyChanged(); + } + } + } + } + + public bool IsEnabled + { + get + { + return _isEnabled; + } + + set + { + if (value != _isEnabled) + { + _isEnabled = value; + GeneralSettingsConfig.Enabled.VideoConference = value; + OutGoingGeneralSettings snd = new OutGoingGeneralSettings(GeneralSettingsConfig); + + SendConfigMSG(snd.ToString()); + OnPropertyChanged(nameof(IsEnabled)); + } + } + } + + public bool IsElevated + { + get + { + return GeneralSettingsConfig.IsElevated; + } + } + + public HotkeySettings CameraAndMicrophoneMuteHotkey + { + get + { + return _cameraAndMicrophoneMuteHotkey; + } + + set + { + if (value != _cameraAndMicrophoneMuteHotkey) + { + _cameraAndMicrophoneMuteHotkey = value; + Settings.Properties.MuteCameraAndMicrophoneHotkey.Value = value; + RaisePropertyChanged(nameof(CameraAndMicrophoneMuteHotkey)); + } + } + } + + public HotkeySettings MicrophoneMuteHotkey + { + get + { + return _mirophoneMuteHotkey; + } + + set + { + if (value != _mirophoneMuteHotkey) + { + _mirophoneMuteHotkey = value; + Settings.Properties.MuteMicrophoneHotkey.Value = value; + RaisePropertyChanged(nameof(MicrophoneMuteHotkey)); + } + } + } + + public HotkeySettings CameraMuteHotkey + { + get + { + return _cameraMuteHotkey; + } + + set + { + if (value != _cameraMuteHotkey) + { + _cameraMuteHotkey = value; + Settings.Properties.MuteCameraHotkey.Value = value; + RaisePropertyChanged(nameof(CameraMuteHotkey)); + } + } + } + + public int ToolbarPostionIndex + { + get + { + return _toolbarPositionIndex; + } + + set + { + if (_toolbarPositionIndex != value) + { + _toolbarPositionIndex = value; + switch (_toolbarPositionIndex) + { + case 0: + Settings.Properties.ToolbarPosition.Value = "Top left corner"; + break; + + case 1: + Settings.Properties.ToolbarPosition.Value = "Top center"; + break; + + case 2: + Settings.Properties.ToolbarPosition.Value = "Top right corner"; + break; + + case 3: + Settings.Properties.ToolbarPosition.Value = "Bottom left corner"; + break; + + case 4: + Settings.Properties.ToolbarPosition.Value = "Bottom center"; + break; + + case 5: + Settings.Properties.ToolbarPosition.Value = "Bottom right corner"; + break; + } + + RaisePropertyChanged(nameof(ToolbarPostionIndex)); + } + } + } + + public int ToolbarMonitorIndex + { + get + { + return _toolbarMonitorIndex; + } + + set + { + if (_toolbarMonitorIndex != value) + { + _toolbarMonitorIndex = value; + switch (_toolbarMonitorIndex) + { + case 0: + Settings.Properties.ToolbarMonitor.Value = "Main monitor"; + break; + + case 1: + Settings.Properties.ToolbarMonitor.Value = "All monitors"; + break; + } + + RaisePropertyChanged(nameof(ToolbarMonitorIndex)); + } + } + } + + public bool HideToolbarWhenUnmuted + { + get + { + return _hideToolbarWhenUnmuted; + } + + set + { + if (value != _hideToolbarWhenUnmuted) + { + _hideToolbarWhenUnmuted = value; + Settings.Properties.HideToolbarWhenUnmuted.Value = value; + RaisePropertyChanged(nameof(HideToolbarWhenUnmuted)); + } + } + } + + public string GetSettingsSubPath() + { + return _settingsConfigFileFolder + "\\" + ModuleName; + } + +#pragma warning disable CA1030 // Use events where appropriate + public void RaisePropertyChanged([CallerMemberName] string propertyName = null) +#pragma warning restore CA1030 // Use events where appropriate + { + OnPropertyChanged(propertyName); + SndVideoConferenceSettings outsettings = new SndVideoConferenceSettings(Settings); + SndModuleSettings ipcMessage = new SndModuleSettings(outsettings); + + SendConfigMSG(ipcMessage.ToJsonString()); + } + } + + [ComImport] + [Guid("3E68D4BD-7135-4D10-8018-9FB6D9F33FA1")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface IInitializeWithWindow + { + void Initialize(IntPtr hwnd); + } +} diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI/Assets/Modules/VideoConference.png b/src/settings-ui/Microsoft.PowerToys.Settings.UI/Assets/Modules/VideoConference.png new file mode 100644 index 0000000000..538653baed Binary files /dev/null and b/src/settings-ui/Microsoft.PowerToys.Settings.UI/Assets/Modules/VideoConference.png differ diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI/Microsoft.PowerToys.Settings.UI.csproj b/src/settings-ui/Microsoft.PowerToys.Settings.UI/Microsoft.PowerToys.Settings.UI.csproj index 650008eb1a..39d1db30dd 100644 --- a/src/settings-ui/Microsoft.PowerToys.Settings.UI/Microsoft.PowerToys.Settings.UI.csproj +++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI/Microsoft.PowerToys.Settings.UI.csproj @@ -199,6 +199,9 @@ ShortcutGuidePage.xaml + + VideoConference.xaml + @@ -240,6 +243,7 @@ + @@ -428,6 +432,10 @@ Designer MSBuild:Compile + + MSBuild:Compile + Designer + diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI/Strings/en-us/Resources.resw b/src/settings-ui/Microsoft.PowerToys.Settings.UI/Strings/en-us/Resources.resw index a021a1bd6a..f107d8066a 100644 --- a/src/settings-ui/Microsoft.PowerToys.Settings.UI/Strings/en-us/Resources.resw +++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI/Strings/en-us/Resources.resw @@ -117,6 +117,98 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + Video Conference Mute + Navigation view item name for Video Conference + + + Enable Video Conference + + + Video Conference Mute is a quick and easy way to do an global "mute" of both your microphone and webcam. +Disabling this module or closing PowerToys will unmute the microphone and camera. + + + Mute camera and microphone + + + Mute microphone + + + Mute camera + + + Selected camera + + + Selected microphone + + + Camera overlay image + + + Toolbar position + + + Top center + + + Top left corner + + + Top right corner + + + Bottom left corner + + + Bottom center + + + Bottom right corner + + + Show toolbar on + + + Main monitor + + + Monitor under cursor + + + Active window monitor + + + All monitors + + + Hide toolbar when both camera and microphone are unmuted + + + About Video Conference + + + Camera + + + Microphone + + + Toolbar + + + Shortcuts + + + Camera overlay image preview + + + Browse + + + Clear + General Navigation view item name for General @@ -948,6 +1040,10 @@ https://aka.ms/PowerToysOverview_ShortcutGuide URL. Do not loc + + https://aka.ms/PowerToysOverview_VideoConference + URL. Do not loc + Win + Up/Down/Left/Right to move windows based on relative position @@ -1304,4 +1400,4 @@ From there, simply click on a Markdown file or SVG icon in the File Explorer and Download and install - \ No newline at end of file + diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI/ViewModels/ShellViewModel.cs b/src/settings-ui/Microsoft.PowerToys.Settings.UI/ViewModels/ShellViewModel.cs index 2750930b7d..9ded11ab04 100644 --- a/src/settings-ui/Microsoft.PowerToys.Settings.UI/ViewModels/ShellViewModel.cs +++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI/ViewModels/ShellViewModel.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Threading.Tasks; using System.Windows.Input; @@ -36,6 +37,14 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels set { Set(ref isBackEnabled, value); } } + public bool IsVideoConferenceBuild + { + get + { + return this != null && File.Exists("modules/VideoConference/VideoConferenceModule.dll"); + } + } + public WinUI.NavigationViewItem Selected { get { return selected; } diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI/Views/ShellPage.xaml b/src/settings-ui/Microsoft.PowerToys.Settings.UI/Views/ShellPage.xaml index 0c8ab004ae..e79d2e8d91 100644 --- a/src/settings-ui/Microsoft.PowerToys.Settings.UI/Views/ShellPage.xaml +++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI/Views/ShellPage.xaml @@ -89,6 +89,14 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +