add new VideoConference module for muting mic/cam

Co-authored-by: PrzemyslawTusinski <61138537+PrzemyslawTusinski@users.noreply.github.com>
This commit is contained in:
yuyoyuppe 2020-08-24 15:59:41 +02:00
parent 0349383d08
commit 795682242e
No known key found for this signature in database
GPG key ID: B240219D92C197D0
116 changed files with 6547 additions and 24 deletions

View file

@ -116,6 +116,11 @@ build:
- 'modules\Microsoft.Launcher.dll'
- 'modules\PowerRename\PowerRenameExt.dll'
- 'modules\ShortcutGuide\ShortcutGuide.dll'
- 'modules\VideoConference\VideoConferenceModule.dll'
- 'modules\VideoConference\VideoConferenceVirtualDriver\VideoConferenceVirtualDriver.dll'
- 'modules\VideoConference\VideoConferenceVirtualDriver\VideoConferenceCustomMediaSource.dll'
- 'modules\VideoConference\VideoConferenceVirtualDriver\videoconferencevirtualdriver.cat'
- 'modules\VideoConference\VideoConferenceVirtualDriver\VideoConferenceVirtualDriver.inf'
- 'Notifications.dll'
- 'os-detection.dll'
- 'PowerToys.exe'

View file

@ -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

View file

@ -265,6 +265,19 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Plugin.Uri.UnitTe
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.PowerToys.Settings.UI.UnitTests", "src\core\Microsoft.PowerToys.Settings.UI.UnitTests\Microsoft.PowerToys.Settings.UI.UnitTests.csproj", "{0F85E674-34AE-443D-954C-8321EB8B93B1}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "VideoConference", "VideoConference", "{05BE6150-D5B3-48F0-AEB9-C44096950C6D}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "VideoConferenceCustomMediaSource", "src\modules\videoconference\VideoConferenceCustomMediaSource\VideoConferenceCustomMediaSource.vcxproj", "{43AD9BF7-E765-48FE-9826-71A8F2CB12DD}"
ProjectSection(ProjectDependencies) = postProject
{459E0768-7EBD-4C41-BBA1-6DB3B3815E0A} = {459E0768-7EBD-4C41-BBA1-6DB3B3815E0A}
EndProjectSection
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "VideoConferenceModule", "src\modules\videoconference\VideoConferenceModule\Video Conference.vcxproj", "{FD2CAFFC-D682-4ED9-A06B-5FC88AE0A193}"
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}") = "VideoConferenceVirtualDriver", "src\modules\videoconference\VideoConferenceVirtualDriver\VideoConferenceVirtualDriver.vcxproj", "{3098C6BF-E96E-4793-A70E-FB09B741580A}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
@ -531,6 +544,24 @@ Global
{0F85E674-34AE-443D-954C-8321EB8B93B1}.Debug|x64.Build.0 = Debug|x64
{0F85E674-34AE-443D-954C-8321EB8B93B1}.Release|x64.ActiveCfg = Release|x64
{0F85E674-34AE-443D-954C-8321EB8B93B1}.Release|x64.Build.0 = Release|x64
{43AD9BF7-E765-48FE-9826-71A8F2CB12DD}.Debug|x64.ActiveCfg = Debug|x64
{43AD9BF7-E765-48FE-9826-71A8F2CB12DD}.Debug|x64.Build.0 = Debug|x64
{43AD9BF7-E765-48FE-9826-71A8F2CB12DD}.Release|x64.ActiveCfg = Release|x64
{43AD9BF7-E765-48FE-9826-71A8F2CB12DD}.Release|x64.Build.0 = Release|x64
{FD2CAFFC-D682-4ED9-A06B-5FC88AE0A193}.Debug|x64.ActiveCfg = Debug|x64
{FD2CAFFC-D682-4ED9-A06B-5FC88AE0A193}.Debug|x64.Build.0 = Debug|x64
{FD2CAFFC-D682-4ED9-A06B-5FC88AE0A193}.Release|x64.ActiveCfg = Release|x64
{FD2CAFFC-D682-4ED9-A06B-5FC88AE0A193}.Release|x64.Build.0 = 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}.Release|x64.ActiveCfg = Release|x64
{459E0768-7EBD-4C41-BBA1-6DB3B3815E0A}.Release|x64.Build.0 = Release|x64
{3098C6BF-E96E-4793-A70E-FB09B741580A}.Debug|x64.ActiveCfg = Debug|x64
{3098C6BF-E96E-4793-A70E-FB09B741580A}.Debug|x64.Build.0 = Debug|x64
{3098C6BF-E96E-4793-A70E-FB09B741580A}.Debug|x64.Deploy.0 = Debug|x64
{3098C6BF-E96E-4793-A70E-FB09B741580A}.Release|x64.ActiveCfg = Release|x64
{3098C6BF-E96E-4793-A70E-FB09B741580A}.Release|x64.Build.0 = Release|x64
{3098C6BF-E96E-4793-A70E-FB09B741580A}.Release|x64.Deploy.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -607,6 +638,11 @@ Global
{03276A39-D4E9-417C-8FFD-200B0EE5E871} = {4AFC9975-2456-4C70-94A4-84073C1CED93}
{B81FB7B6-D30E-428F-908A-41422EFC1172} = {4AFC9975-2456-4C70-94A4-84073C1CED93}
{0F85E674-34AE-443D-954C-8321EB8B93B1} = {C3081D9A-1586-441A-B5F4-ED815B3719C1}
{05BE6150-D5B3-48F0-AEB9-C44096950C6D} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
{43AD9BF7-E765-48FE-9826-71A8F2CB12DD} = {05BE6150-D5B3-48F0-AEB9-C44096950C6D}
{FD2CAFFC-D682-4ED9-A06B-5FC88AE0A193} = {05BE6150-D5B3-48F0-AEB9-C44096950C6D}
{459E0768-7EBD-4C41-BBA1-6DB3B3815E0A} = {05BE6150-D5B3-48F0-AEB9-C44096950C6D}
{3098C6BF-E96E-4793-A70E-FB09B741580A} = {05BE6150-D5B3-48F0-AEB9-C44096950C6D}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0}

View file

@ -2,8 +2,9 @@
<Project ToolsVersion="4.0" DefaultTargets="Build" InitialTargets="EnsureNuGetPackageBuildImports" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\packages\WiX.3.11.2\build\wix.props" Condition="Exists('..\packages\WiX.3.11.2\build\wix.props')" />
<Import Project="..\..\src\Version.props" />
<Import Project="..\..\src\hasWDK.props" />
<PropertyGroup>
<DefineConstants>Version=$(Version);</DefineConstants>
<DefineConstants>Version=$(Version);HasWDK=$(HasWDK)</DefineConstants>
</PropertyGroup>
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Release</Configuration>

View file

@ -9,6 +9,7 @@
<?define PowerRenameProjectName="PowerRename"?>
<?define ShortcutGuideProjectName="ShortcutGuide"?>
<?define ColorPickerProjectName="ColorPicker"?>
<?define VideoConferenceProjectName="VideoConference"?>
<?define RepoDir="$(var.ProjectDir)..\..\" ?>
<?define BinX64Dir="$(var.RepoDir)x64\$(var.Configuration)\" ?>
@ -87,6 +88,20 @@
<Custom Action="RegisterPowerToysSchTask" After="InstallFiles">
NOT Installed and CREATESCHEDULEDTASK = 1
</Custom>
<?if $(var.HasWDK)="true" ?>
<Custom Action="SetCertifyVirtualCameraDriverParam" After="RegisterPowerToysSchTask"/>
<Custom Action="CertifyVirtualCameraDriver" After="SetCertifyVirtualCameraDriverParam" >
NOT Installed
</Custom>
<Custom Action="SetInstallVirtualCameraDriverParam" After="CertifyVirtualCameraDriver" />
<Custom Action="InstallVirtualCameraDriver" After="SetInstallVirtualCameraDriverParam" >
NOT Installed
</Custom>
<Custom Action="SetUninstallVirtualCameraDriverParam" Before="UninstallVirtualCameraDriver" />
<Custom Action="UninstallVirtualCameraDriver" Before="RemoveFiles">
Installed
</Custom>
<?endif?>
<Custom Action="WixCloseApplications" Before="RemoveFiles" />
<Custom Action="RemovePowerToysSchTasks" After="RemoveFiles" />
<Custom Action="TelemetryLogInstallSuccess" After="InstallFinalize">
@ -204,6 +219,44 @@
DllEntry="DetectPrevInstallPathCA"
/>
<?if $(var.HasWDK)="true" ?>
<CustomAction Id="SetCertifyVirtualCameraDriverParam"
Property="CertifyVirtualCameraDriver"
Value="[#VideoConferenceVirtualDriver.cer]" />
<CustomAction Id="CertifyVirtualCameraDriver"
Return="ignore"
Impersonate="no"
Execute="deferred"
BinaryKey="PTCustomActions"
DllEntry="CertifyVirtualCameraDriverCA"
/>
<CustomAction Id="SetInstallVirtualCameraDriverParam"
Property="InstallVirtualCameraDriver"
Value="[#VideoConferenceVirtualDriver.inf]" />
<CustomAction Id="InstallVirtualCameraDriver"
Return="ignore"
Impersonate="no"
Execute="deferred"
BinaryKey="PTCustomActions"
DllEntry="InstallVirtualCameraDriverCA"
/>
<CustomAction Id="SetUninstallVirtualCameraDriverParam"
Property="UninstallVirtualCameraDriver"
Value="[#VideoConferenceVirtualDriver.inf]" />
<CustomAction Id="UninstallVirtualCameraDriver"
Return="ignore"
Impersonate="no"
Execute="deferred"
BinaryKey="PTCustomActions"
DllEntry="UninstallVirtualCameraDriverCA"
/>
<?endif?>
<!-- Close 'PowerToys.exe' before uninstall-->
<Property Id="MSIRESTARTMANAGERCONTROL" Value="Disable" />
<util:CloseApplication CloseMessage="yes" Target="PowerToys.exe" ElevatedCloseMessage="yes" RebootPrompt="no" TerminateProcess="0" />
@ -240,6 +293,11 @@
</Directory>
<Directory Id="PowerRenameInstallFolder" Name="$(var.PowerRenameProjectName)"/>
<Directory Id="ShortcutGuideInstallFolder" Name="$(var.ShortcutGuideProjectName)"/>
<?if $(var.HasWDK)="true" ?>
<Directory Id="VideoConferenceInstallFolder" Name="$(var.VideoConferenceProjectName)">
<Directory Id="VideoConferenceIconsFolder" Name="Icons" />
</Directory>
<?endif?>
<Directory Id="FileExplorerPreviewInstallFolder" Name="FileExplorerPreview" />
<Directory Id="FancyZonesInstallFolder" Name="$(var.FancyZonesProjectName)" />
<Directory Id="KeyboardManagerInstallFolder" Name="$(var.KeyboardManagerProjectName)" />
@ -612,6 +670,36 @@
<File Source="$(var.BinX64Dir)modules\$(var.ShortcutGuideProjectName)\$(var.ShortcutGuideProjectName).dll" KeyPath="yes" />
</Component>
</DirectoryRef>
<?if $(var.HasWDK)="true" ?>
<DirectoryRef Id="VideoConferenceInstallFolder" FileSource="$(var.BinX64Dir)modules\$(var.VideoConferenceProjectName)\">
<Component Id="Module_VideoConference" Guid="5996527a-40fc-432e-b3ac-abc0b4bd3887" Win64="yes">
<File Source="$(var.BinX64Dir)modules\$(var.VideoConferenceProjectName)\VideoConferenceModule.dll" KeyPath="yes" />
<File Source="$(var.BinX64Dir)modules\$(var.VideoConferenceProjectName)\black.bmp" />
<File Source="$(var.BinX64Dir)modules\$(var.VideoConferenceProjectName)\VideoConferenceVirtualDriver\videoconferencevirtualdriver.cat" />
<File Source="$(var.BinX64Dir)modules\$(var.VideoConferenceProjectName)\VideoConferenceVirtualDriver\VideoConferenceCustomMediaSource.dll" />
<File Source="$(var.BinX64Dir)modules\$(var.VideoConferenceProjectName)\VideoConferenceVirtualDriver\VideoConferenceVirtualDriver.dll" />
<File Source="$(var.BinX64Dir)modules\$(var.VideoConferenceProjectName)\VideoConferenceVirtualDriver\VideoConferenceVirtualDriver.inf" />
<File Source="$(var.BinX64Dir)modules\$(var.VideoConferenceProjectName)\VideoConferenceVirtualDriver.cer" />
</Component>
</DirectoryRef>
<DirectoryRef Id="VideoConferenceIconsFolder" FileSource="$(var.BinX64Dir)modules\$(var.VideoConferenceProjectName)\Icons">
<Component Id="Module_VideoConferenceIcons" Guid="5996527a-40fc-432e-b34c-abc0b4bd3887" Win64="yes">
<File Source="$(var.BinX64Dir)modules\$(var.VideoConferenceProjectName)\Icons\Off-NotInUse Dark.png" />
<File Source="$(var.BinX64Dir)modules\$(var.VideoConferenceProjectName)\Icons\Off-NotInUse Light.png" />
<File Source="$(var.BinX64Dir)modules\$(var.VideoConferenceProjectName)\Icons\Off-Off Dark.png" />
<File Source="$(var.BinX64Dir)modules\$(var.VideoConferenceProjectName)\Icons\Off-Off Light.png" />
<File Source="$(var.BinX64Dir)modules\$(var.VideoConferenceProjectName)\Icons\Off-On Dark.png" />
<File Source="$(var.BinX64Dir)modules\$(var.VideoConferenceProjectName)\Icons\Off-On Light.png" />
<File Source="$(var.BinX64Dir)modules\$(var.VideoConferenceProjectName)\Icons\On-NotInUse Dark.png" />
<File Source="$(var.BinX64Dir)modules\$(var.VideoConferenceProjectName)\Icons\On-NotInUse Light.png" />
<File Source="$(var.BinX64Dir)modules\$(var.VideoConferenceProjectName)\Icons\On-Off Light.png" />
<File Source="$(var.BinX64Dir)modules\$(var.VideoConferenceProjectName)\Icons\On-Off Dark.png" />
<File Source="$(var.BinX64Dir)modules\$(var.VideoConferenceProjectName)\Icons\On-On Dark.png" />
<File Source="$(var.BinX64Dir)modules\$(var.VideoConferenceProjectName)\Icons\On-On Light.png" />
</Component>
</DirectoryRef>
<?endif?>
<DirectoryRef Id="KeyboardManagerInstallFolder" FileSource="$(var.BinX64Dir)modules\$(var.KeyboardManagerProjectName)\">
<Component Id="Module_KeyboardManager" Guid="9279BD82-786F-4F0B-8E49-DB484EE34C9B" Win64="yes">
@ -793,6 +881,10 @@
<ComponentRef Id="vcredist_dlls" />
<ComponentRef Id="PowerToysSvgs" />
<ComponentRef Id="Module_ShortcutGuide" />
<?if $(var.HasWDK)="true" ?>
<ComponentRef Id="Module_VideoConference" />
<ComponentRef Id="Module_VideoConferenceIcons" />
<?endif?>
<ComponentRef Id="Module_FancyZones" />
<ComponentRef Id="DesktopShortcut" />
<ComponentRef Id="Module_PowerRename" />

View file

@ -31,7 +31,6 @@ echo ^</Project^> >> !settingsPublishProfile!
rem In case of Release we should not use Debug CRT in VCRT forwarders
msbuild !PTRoot!\src\core\Microsoft.PowerToys.Settings.UI.Runner\Microsoft.PowerToys.Settings.UI.Runner.csproj -t:Publish -p:Configuration="Release" -p:Platform="x64" -p:AppxBundle=Never -p:VCRTForwarders-IncludeDebugCRT=false -p:PublishProfile=!settingsProfileFileName!
rem Publish Launcher
SET launcherProfileFolderName=!PTRoot!\src\modules\launcher\PowerLauncher\Properties\PublishProfiles\

View file

@ -3,6 +3,11 @@
#include <ProjectTelemetry.h>
#include "../../src/common/updating/updating.h"
#include <newdev.h>
#include <Windows.h>
#pragma comment (lib, "crypt32.lib")
using namespace std;
@ -16,6 +21,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.
@ -564,6 +570,8 @@ LExit:
return WcaFinalize(er);
}
UINT __stdcall DetectPrevInstallPathCA(MSIHANDLE hInstall)
{
HRESULT hr = S_OK;
@ -662,6 +670,165 @@ UINT __stdcall TerminateProcessesCA(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 = NULL;
HCERTSTORE hCertStore = NULL;
HANDLE hfile = NULL;
DWORD size = INVALID_FILE_SIZE;
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, NULL, 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, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hfile == INVALID_HANDLE_VALUE)
{
hr = GetLastError();
ExitOnFailure(hr, "Certificate file open failed", hr);
}
size = GetFileSize(hfile, NULL);
if (size == INVALID_FILE_SIZE)
{
hr = GetLastError();
ExitOnFailure(hr, "Certificate file size not valid", hr);
}
char* pFileContent = (char*)malloc(size);
DWORD sizeread;
if (!ReadFile(hfile, pFileContent, size, &sizeread, NULL))
{
hr = GetLastError();
ExitOnFailure(hr, "Certificate file read failed", hr);
}
if (!CertAddEncodedCertificateToStore(hCertStore,
X509_ASN_ENCODING,
(const BYTE*)pFileContent,
size,
CERT_STORE_ADD_ALWAYS,
NULL))
{
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;
hr = WcaInitialize(hInstall, "InstallVirtualCameraDriverCA");
ExitOnFailure(hr, "Failed to initialize");
LPWSTR driverPath = NULL;
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;
hr = WcaInitialize(hInstall, "UninstallVirtualCameraDriverCA");
ExitOnFailure(hr, "Failed to initialize");
LPWSTR driverPath = NULL;
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);
}
// DllMain - Initialize and cleanup WiX custom action utils.
extern "C" BOOL WINAPI DllMain(__in HINSTANCE hInst, __in ULONG ulReason, __in LPVOID)

View file

@ -12,4 +12,7 @@ EXPORTS
TelemetryLogUninstallFailCA
TelemetryLogRepairCancelCA
TelemetryLogRepairFailCA
TerminateProcessesCA
TerminateProcessesCA
CertifyVirtualCameraDriverCA
InstallVirtualCameraDriverCA
UninstallVirtualCameraDriverCA

View file

@ -60,7 +60,7 @@
<TreatWarningAsError>true</TreatWarningAsError>
</ClCompile>
<Link>
<AdditionalDependencies>Psapi.lib;Pathcch.lib;comsupp.lib;taskschd.lib;Secur32.lib;msi.lib;dutil.lib;wcautil.lib;Version.lib;..\..\$(PlatformShortName)\$(Configuration)\common.lib;..\..\$(PlatformShortName)\$(Configuration)\updating.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>Psapi.lib;Pathcch.lib;comsupp.lib;taskschd.lib;Secur32.lib;msi.lib;dutil.lib;wcautil.lib;Version.lib;Newdev.lib;..\..\$(PlatformShortName)\$(Configuration)\common.lib;..\..\$(PlatformShortName)\$(Configuration)\updating.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>$(WIX)sdk\$(WixPlatformToolset)\lib\x64;$(SolutionDir)\packages\WiX.3.11.2\tools\sdk\vs2017\lib\x64;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<ModuleDefinitionFile>CustomAction.def</ModuleDefinitionFile>
<GenerateDebugInformation>true</GenerateDebugInformation>
@ -81,8 +81,11 @@
<LanguageStandard>stdcpplatest</LanguageStandard>
<TreatWarningAsError>true</TreatWarningAsError>
</ClCompile>
<ClCompile Condition="'$(CIBuild)'=='true'">
<AdditionalOptions >/DCIBuild</AdditionalOptions>
</ClCompile>
<Link>
<AdditionalDependencies>Psapi.lib;Pathcch.lib;comsupp.lib;taskschd.lib;Secur32.lib;msi.lib;dutil.lib;wcautil.lib;Version.lib;..\..\$(PlatformShortName)\$(Configuration)\common.lib;..\..\$(PlatformShortName)\$(Configuration)\updating.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>Psapi.lib;Pathcch.lib;comsupp.lib;taskschd.lib;Secur32.lib;msi.lib;dutil.lib;wcautil.lib;Version.lib;Newdev.lib;..\..\$(PlatformShortName)\$(Configuration)\common.lib;..\..\$(PlatformShortName)\$(Configuration)\updating.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>$(WIX)sdk\$(WixPlatformToolset)\lib\x64;$(SolutionDir)\packages\WiX.3.11.2\tools\sdk\vs2017\lib\x64;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<ModuleDefinitionFile>CustomAction.def</ModuleDefinitionFile>
<GenerateDebugInformation>true</GenerateDebugInformation>

View file

@ -132,6 +132,7 @@
<ClInclude Include="keyboard_layout.h" />
<ClInclude Include="keyboard_layout_impl.h" />
<ClInclude Include="LowlevelKeyboardEvent.h" />
<ClInclude Include="naming.h" />
<ClInclude Include="notifications.h" />
<ClInclude Include="processApi.h" />
<ClInclude Include="RcResource.h" />
@ -141,6 +142,7 @@
<ClInclude Include="string_utils.h" />
<ClInclude Include="timeutil.h" />
<ClInclude Include="two_way_pipe_message_ipc.h" />
<ClInclude Include="user.h" />
<ClInclude Include="VersionHelper.h" />
<ClInclude Include="window_helpers.h" />
<ClInclude Include="icon_helpers.h" />
@ -171,6 +173,7 @@
<ClCompile Include="json.cpp" />
<ClCompile Include="keyboard_layout.cpp" />
<ClCompile Include="monitors.cpp" />
<ClCompile Include="naming.cpp" />
<ClCompile Include="notifications.cpp" />
<ClCompile Include="on_thread_executor.cpp" />
<ClCompile Include="os-detect.cpp" />
@ -185,6 +188,7 @@
<ClCompile Include="start_visible.cpp" />
<ClCompile Include="tasklist_positions.cpp" />
<ClCompile Include="common.cpp" />
<ClCompile Include="user.cpp" />
<ClCompile Include="version.cpp" />
<ClCompile Include="two_way_pipe_message_ipc.cpp" />
<ClCompile Include="VersionHelper.cpp" />

View file

@ -141,6 +141,12 @@
<ClInclude Include="string_utils.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="naming.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="user.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="d2d_svg.cpp">
@ -222,8 +228,14 @@
<ClCompile Include="comUtils.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="naming.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="user.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
</Project>
</Project>

View file

@ -72,3 +72,23 @@ MonitorInfo MonitorInfo::GetPrimaryMonitor()
EnumDisplayMonitors(NULL, NULL, GetPrimaryDisplayEnumCb, reinterpret_cast<LPARAM>(&primary));
return primary;
}
MonitorInfo MonitorInfo::GetFromWindow(HWND hwnd)
{
auto monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
return GetFromHandle(monitor);
}
MonitorInfo MonitorInfo::GetFromPoint(POINT p)
{
auto monitor = MonitorFromPoint(p, MONITOR_DEFAULTTONEAREST);
return GetFromHandle(monitor);
}
MonitorInfo MonitorInfo::GetFromHandle(HMONITOR monitor)
{
MONITORINFOEX monitor_info;
monitor_info.cbSize = sizeof(MONITORINFOEX);
GetMonitorInfo(monitor, &monitor_info);
return MonitorInfo(monitor, monitor_info.rcWork);
}

View file

@ -33,6 +33,9 @@ struct MonitorInfo : ScreenSize
// Returns monitor rects ordered from left to right
static std::vector<MonitorInfo> GetMonitors(bool includeNonWorkingArea);
static MonitorInfo GetPrimaryMonitor();
static MonitorInfo GetFromWindow(HWND hwnd);
static MonitorInfo GetFromPoint(POINT p);
static MonitorInfo GetFromHandle(HMONITOR monitor);
};
bool operator==(const ScreenSize& lhs, const ScreenSize& rhs);

20
src/common/naming.cpp Normal file
View file

@ -0,0 +1,20 @@
#include "pch.h"
#include "naming.h"
#include "user.h"
std::wstring ObtainStableGlobalNameForKernelObject(const std::wstring_view name, const bool restricted)
{
static const std::optional<std::wstring> username = ObtainActiveUserName();
std::wstring result = L"Global\\";
if (restricted)
{
result += L"Restricted\\";
}
if (username)
{
result += *username;
}
result += name;
return result;
}

5
src/common/naming.h Normal file
View file

@ -0,0 +1,5 @@
#pragma once
#include <string_view>
#include <string>
std::wstring ObtainStableGlobalNameForKernelObject(const std::wstring_view name, const bool restricted);

View file

@ -7,6 +7,7 @@
namespace PTSettingsHelper
{
std::wstring get_module_save_folder_location(std::wstring_view powertoy_name);
std::wstring get_module_save_file_location(std::wstring_view powertoy_name);
std::wstring get_root_save_folder_location();
void save_module_settings(std::wstring_view powertoy_name, json::JsonObject& settings);

View file

@ -202,7 +202,7 @@ namespace PowerToysSettings
if (output_bytes == 1 && output[0] >= 'a' && output[0] <= 'z')
{
// Make Latin letters keys capital, as it looks better
output[0] = toupper(output[0]);
output[0] = static_cast<wchar_t>(toupper(output[0]));
}
return output.data();
}

View file

@ -26,6 +26,13 @@ namespace timeutil
return std::nullopt;
}
}
inline time_t from_filetime(const FILETIME& ft)
{
ULARGE_INTEGER ull;
ull.LowPart = ft.dwLowDateTime;
ull.HighPart = ft.dwHighDateTime;
return ull.QuadPart / 10000000ULL - 11644473600ULL;
}
inline std::time_t now()
{

20
src/common/user.cpp Normal file
View file

@ -0,0 +1,20 @@
#include "pch.h"
#include "user.h"
#include <wtsapi32.h>
std::optional<std::wstring> 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;
}

9
src/common/user.h Normal file
View file

@ -0,0 +1,9 @@
#pragma once
#include <optional>
#include <string>
#include <string_view>
std::optional<std::wstring> ObtainActiveUserName();
std::wstring ObtainStableGlobalNameForKernelObject(const std::wstring_view name, const bool restricted);

View file

@ -80,6 +80,22 @@ namespace Microsoft.PowerToys.Settings.UI.Lib
}
}
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

View file

@ -56,6 +56,22 @@ namespace Microsoft.PowerToys.Settings.UI.Lib
return JsonSerializer.Deserialize<T>(jsonSettingsString);
}
public static T GetOrCreateSettings<T>(string powertoy = DefaultModuleName, string fileName = DefaultFileName)
where T : new()
{
try
{
var jsonSettingsString = File.ReadAllText(GetSettingsPath(powertoy, fileName));
return JsonSerializer.Deserialize<T>(jsonSettingsString);
}
catch
{
var settings = new T();
SaveSettings(JsonSerializer.Serialize(settings), powertoy, fileName);
return settings;
}
}
// Save settings to a json file.
public static void SaveSettings(string jsonSettings, string powertoy = DefaultModuleName, string fileName = DefaultFileName)
{

View file

@ -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.Lib
{
public class SndVideoConferenceSettings
{
[JsonPropertyName("Video Conference")]
public VideoConferenceSettings VideoConference { get; set; }
public SndVideoConferenceSettings(VideoConferenceSettings settings)
{
this.VideoConference = settings;
}
public string ToJsonString()
{
return JsonSerializer.Serialize(this);
}
}
}

View file

@ -29,5 +29,10 @@ namespace Microsoft.PowerToys.Settings.UI.Lib
{
return JsonSerializer.Serialize(this);
}
public static implicit operator StringProperty(string v)
{
return new StringProperty(v);
}
}
}

View file

@ -0,0 +1,85 @@
// 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.Lib
{
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,
});
Theme = new StringProperty("light");
this.HideOverlayWhenUnmuted = 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("overlay_position")]
public StringProperty OverlayPosition { get; set; } = "Top right corner";
[JsonPropertyName("overlay_monitor")]
public StringProperty OverlayMonitor { 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_overlay_when_unmuted")]
public BoolProperty HideOverlayWhenUnmuted { get; set; }
// converts the current to a json string.
public string ToJsonString()
{
return JsonSerializer.Serialize(this);
}
}
}

View file

@ -0,0 +1,36 @@
// 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.Lib
{
public class VideoConferenceSettings
{
public VideoConferenceSettings()
{
this.Version = "1.0";
this.Name = "Video Conference";
this.Properties = new VideoConferenceConfigProperties();
}
[JsonPropertyName("version")]
public string Version { get; set; }
[JsonPropertyName("name")]
public string Name { get; set; }
[JsonPropertyName("properties")]
public VideoConferenceConfigProperties Properties { get; set; }
public string ToJsonString()
{
return JsonSerializer.Serialize(this);
}
}
}

View file

@ -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.Lib
{
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);
}
}
}

View file

@ -111,6 +111,7 @@
<Compile Include="Services\NavigationService.cs" />
<Compile Include="ViewModels\Commands\ButtonClickCommand.cs" />
<Compile Include="ViewModels\ShellViewModel.cs" />
<Compile Include="ViewModels\VideoConferenceViewModel.cs" />
<Compile Include="Views\ColorPickerPage.xaml.cs">
<DependentUpon>ColorPickerPage.xaml</DependentUpon>
</Compile>
@ -141,6 +142,9 @@
<Compile Include="Views\ShortcutGuidePage.xaml.cs">
<DependentUpon>ShortcutGuidePage.xaml</DependentUpon>
</Compile>
<Compile Include="Views\VideoConference.xaml.cs">
<DependentUpon>VideoConference.xaml</DependentUpon>
</Compile>
<Compile Include="Views\VisibleIfNotEmpty.cs" />
</ItemGroup>
<ItemGroup>
@ -182,7 +186,7 @@
<Version>6.1.0</Version>
</PackageReference>
<PackageReference Include="Microsoft.Toolkit.Win32.UI.XamlApplication">
<Version>6.1.1</Version>
<Version>6.1.2</Version>
</PackageReference>
<PackageReference Include="Microsoft.UI.Xaml">
<Version>2.5.0-prerelease.200708003</Version>
@ -275,6 +279,10 @@
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Views\VideoConference.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
</ItemGroup>
<ItemGroup>
<AdditionalFiles Include="..\..\codeAnalysis\StyleCop.json">
@ -298,12 +306,6 @@
<EnableTypeInfoReflection>false</EnableTypeInfoReflection>
<EnableXBindDiagnostics>false</EnableXBindDiagnostics>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|Win32'">
<LangVersion>8.0</LangVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|Win32'">
<LangVersion>8.0</LangVersion>
</PropertyGroup>
<Import Project="$(MSBuildExtensionsPath)\Microsoft\WindowsXaml\v$(VisualStudioVersion)\Microsoft.Windows.UI.Xaml.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.

View file

@ -141,6 +141,10 @@
<value>PowerRename</value>
<comment>Navigation view item name for PowerRename</comment>
</data>
<data name="Shell_VideoConference.Content" xml:space="preserve">
<value>Video Conference</value>
<comment>Navigation view item name for Video Conference</comment>
</data>
<data name="Shell_ShortcutGuide.Content" xml:space="preserve">
<value>Shortcut Guide</value>
<comment>Navigation view item name for Shortcut Guide</comment>
@ -468,6 +472,67 @@
<data name="ShortcutGuide_Theme.Text" xml:space="preserve">
<value>Choose Shortcut Guide overlay color</value>
</data>
<data name="VideoConference_Enable.Header" xml:space="preserve">
<value>Enable Video Conference</value>
</data>
<data name="VideoConference_Description.Text" xml:space="preserve">
<value>Tool that allows muting/unmuting microphone and camera
Disabling module or closing PowerToys will unmute microphone and camera</value>
</data>
<data name="VideoConference_CameraAndMicrophoneMuteHotkeyControl_Header.Header" xml:space="preserve">
<value>Edit camera and microphone mute hotkey</value>
</data>
<data name="VideoConference_MicrophoneMuteHotkeyControl_Header.Header" xml:space="preserve">
<value>Edit microphone mute hotkey</value>
</data>
<data name="VideoConference_CameraMuteHotkeyControl_Header.Header" xml:space="preserve">
<value>Edit camera mute hotkey</value>
</data>
<data name="VideoConference_SelectedCamera.Header" xml:space="preserve">
<value>Selected camera</value>
</data>
<data name="VideoConference_CameraOverlayImagePathHeader.Text" xml:space="preserve">
<value>Camera overlay image</value>
</data>
<data name="VideoConference_OverlayPosition.Header" xml:space="preserve">
<value>Overlay postion</value>
</data>
<data name="VideoConference_OverlayPosition_TopCenter.Content" xml:space="preserve">
<value>Top center</value>
</data>
<data name="VideoConference_OverlayPosition_TopLeftCorner.Content" xml:space="preserve">
<value>Top left corner</value>
</data>
<data name="VideoConference_OverlayPosition_TopRightCorner.Content" xml:space="preserve">
<value>Top right corner</value>
</data>
<data name="VideoConference_OverlayPosition_BottomLeftCorner.Content" xml:space="preserve">
<value>Bottom left corner</value>
</data>
<data name="VideoConference_OverlayPosition_BottomCenter.Content" xml:space="preserve">
<value>Bottom center</value>
</data>
<data name="VideoConference_OverlayPosition_BottomRightCorner.Content" xml:space="preserve">
<value>Bottom right corner</value>
</data>
<data name="VideoConference_OverlayMonitor.Header" xml:space="preserve">
<value>Show overlay on</value>
</data>
<data name="VideoConference_OverlayMonitor_Main.Content" xml:space="preserve">
<value>Main monitor</value>
</data>
<data name="VideoConference_OverlayMonitor_UnderCursor.Content" xml:space="preserve">
<value>Monitor under cursor</value>
</data>
<data name="VideoConference_OverlayMonitor_ActiveWindow.Content" xml:space="preserve">
<value>Active window monitor</value>
</data>
<data name="VideoConference_OverlayMonitor_All.Content" xml:space="preserve">
<value>All monitors</value>
</data>
<data name="VideoConference_HideOverlayWhenUnmuted.Content" xml:space="preserve">
<value>Hide overlay when both camera and microphone are unmuted</value>
</data>
<data name="ImageResizer_CustomSizes.Text" xml:space="preserve">
<value>Image sizes</value>
</data>
@ -651,6 +716,9 @@
<data name="Shortcut_Guide_Image.AutomationProperties.Name" xml:space="preserve">
<value>Shortcut Guide</value>
</data>
<data name="About_VideoConference.Text" xml:space="preserve">
<value>About Video Conference</value>
</data>
<data name="General_Repository.Text" xml:space="preserve">
<value>GitHub repository</value>
</data>

View file

@ -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 File.Exists("modules/VideoConference/VideoConferenceModule.dll");
}
}
public WinUI.NavigationViewItem Selected
{
get { return selected; }

View file

@ -0,0 +1,389 @@
// 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.Threading.Tasks;
using Microsoft.PowerToys.Settings.UI.Lib;
using Microsoft.PowerToys.Settings.UI.Lib.Helpers;
using Microsoft.PowerToys.Settings.UI.Lib.ViewModels.Commands;
using Windows.Devices.Enumeration;
using Windows.Storage.Pickers;
namespace Microsoft.PowerToys.Settings.UI.ViewModels
{
public class VideoConferenceViewModel : Observable
{
private VideoConferenceSettings Settings { get; set; }
private const string ModuleName = "Video Conference";
private const string ProxyCameraName = "PowerToys VideoConference";
private Func<string, int> SendConfigMSG { get; }
private string _settingsConfigFileFolder = string.Empty;
public VideoConferenceViewModel(Func<string, int> ipcMSGCallBackFunc, string configFileSubfolder = "")
{
SendConfigMSG = ipcMSGCallBackFunc;
_settingsConfigFileFolder = configFileSubfolder;
try
{
Settings = SettingsUtils.GetSettings<VideoConferenceSettings>(GetSettingsSubPath());
}
catch
{
Settings = new VideoConferenceSettings();
SettingsUtils.SaveSettings(Settings.ToJsonString(), GetSettingsSubPath());
}
CameraNames = Task.Run(() => GetAllCameras()).Result.Select(di => di.Name).ToList();
// Uncomment to have an additional webcam to debug with! (don't forget to install it)
// CameraNames.Add("DroidCam Source 3");
if (Settings.Properties.SelectedCamera.Value == string.Empty && CameraNames.Count != 0)
{
_selectedCameraIndex = 0;
Settings.Properties.SelectedCamera.Value = CameraNames[0];
SettingsUtils.SaveSettings(Settings.ToJsonString(), ModuleName);
}
else
{
_selectedCameraIndex = CameraNames.FindIndex(name => name == Settings.Properties.SelectedCamera.Value);
}
GeneralSettings generalSettings;
try
{
generalSettings = SettingsUtils.GetSettings<GeneralSettings>(string.Empty);
}
catch
{
generalSettings = new GeneralSettings();
SettingsUtils.SaveSettings(generalSettings.ToJsonString(), string.Empty);
}
this._isEnabled = generalSettings.Enabled.VideoConference;
this._cameraAndMicrophoneMuteHotkey = Settings.Properties.MuteCameraAndMicrophoneHotkey.Value;
this._mirophoneMuteHotkey = Settings.Properties.MuteMicrophoneHotkey.Value;
this._cameraMuteHotkey = Settings.Properties.MuteCameraHotkey.Value;
this.CameraImageOverlayPath = Settings.Properties.CameraOverlayImagePath.Value;
this.SelectOverlayImage = new ButtonClickCommand(SelectOverlayImageAction);
this.ClearOverlayImage = new ButtonClickCommand(ClearOverlayImageAction);
this._hideOverlayWhenUnmuted = Settings.Properties.HideOverlayWhenUnmuted.Value;
switch (Settings.Properties.OverlayPosition.Value)
{
case "Top left corner":
_overlayPositionIndex = 0;
break;
case "Top center":
_overlayPositionIndex = 1;
break;
case "Top right corner":
_overlayPositionIndex = 2;
break;
case "Bottom left corner":
_overlayPositionIndex = 3;
break;
case "Bottom center":
_overlayPositionIndex = 4;
break;
case "Bottom right corner":
_overlayPositionIndex = 5;
break;
}
switch (Settings.Properties.OverlayMonitor.Value)
{
case "Main monitor":
_overlayMonitorIndex = 0;
break;
case "All monitors":
_overlayMonitorIndex = 1;
break;
}
}
private static async Task<List<DeviceInformation>> GetAllCameras()
{
var allCameras = await DeviceInformation.FindAllAsync(DeviceClass.VideoCapture);
return allCameras.Where(cameraInfo => cameraInfo.Name != ProxyCameraName).ToList();
}
private bool _isEnabled = false;
private int _overlayPositionIndex;
private int _overlayMonitorIndex;
private HotkeySettings _cameraAndMicrophoneMuteHotkey;
private HotkeySettings _mirophoneMuteHotkey;
private HotkeySettings _cameraMuteHotkey;
private int _selectedCameraIndex = -1;
private bool _hideOverlayWhenUnmuted;
public List<string> CameraNames { 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("CameraImageOverlayPath");
}
private async void SelectOverlayImageAction()
{
try
{
FileOpenPicker openPicker = new FileOpenPicker
{
ViewMode = PickerViewMode.Thumbnail,
SuggestedStartLocation = PickerLocationId.ComputerFolder,
};
openPicker.FileTypeFilter.Add(".jpg");
openPicker.FileTypeFilter.Add(".jpeg");
openPicker.FileTypeFilter.Add(".png");
#pragma warning disable CS0436 // Type conflicts with imported type
((IInitializeWithWindow)(object)openPicker).Initialize(System.Diagnostics.Process.GetCurrentProcess().MainWindowHandle);
#pragma warning restore CS0436 // Type conflicts with imported type
var pickedImage = await openPicker.PickSingleFileAsync();
if (pickedImage != null)
{
CameraImageOverlayPath = pickedImage.Path;
Settings.Properties.CameraOverlayImagePath = pickedImage.Path;
RaisePropertyChanged("CameraImageOverlayPath");
}
}
catch
{
}
}
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 bool IsEnabled
{
get
{
return _isEnabled;
}
set
{
if (value != _isEnabled)
{
_isEnabled = value;
GeneralSettings generalSettings = SettingsUtils.GetSettings<GeneralSettings>(string.Empty);
generalSettings.Enabled.VideoConference = value;
OutGoingGeneralSettings snd = new OutGoingGeneralSettings(generalSettings);
SendConfigMSG(snd.ToString());
OnPropertyChanged("IsEnabled");
}
}
}
public HotkeySettings CameraAndMicrophoneMuteHotkey
{
get
{
return _cameraAndMicrophoneMuteHotkey;
}
set
{
if (value != _cameraAndMicrophoneMuteHotkey)
{
_cameraAndMicrophoneMuteHotkey = value;
Settings.Properties.MuteCameraAndMicrophoneHotkey.Value = value;
RaisePropertyChanged();
}
}
}
public HotkeySettings MicrophoneMuteHotkey
{
get
{
return _mirophoneMuteHotkey;
}
set
{
if (value != _mirophoneMuteHotkey)
{
_mirophoneMuteHotkey = value;
Settings.Properties.MuteMicrophoneHotkey.Value = value;
RaisePropertyChanged();
}
}
}
public HotkeySettings CameraMuteHotkey
{
get
{
return _cameraMuteHotkey;
}
set
{
if (value != _cameraMuteHotkey)
{
_cameraMuteHotkey = value;
Settings.Properties.MuteCameraHotkey.Value = value;
RaisePropertyChanged();
}
}
}
public int OverlayPostionIndex
{
get
{
return _overlayPositionIndex;
}
set
{
if (_overlayPositionIndex != value)
{
_overlayPositionIndex = value;
switch (_overlayPositionIndex)
{
case 0:
Settings.Properties.OverlayPosition.Value = "Top left corner";
RaisePropertyChanged();
break;
case 1:
Settings.Properties.OverlayPosition.Value = "Top center";
RaisePropertyChanged();
break;
case 2:
Settings.Properties.OverlayPosition.Value = "Top right corner";
RaisePropertyChanged();
break;
case 3:
Settings.Properties.OverlayPosition.Value = "Bottom left corner";
RaisePropertyChanged();
break;
case 4:
Settings.Properties.OverlayPosition.Value = "Bottom center";
RaisePropertyChanged();
break;
case 5:
Settings.Properties.OverlayPosition.Value = "Bottom right corner";
RaisePropertyChanged();
break;
}
}
}
}
public int OverlayMonitorIndex
{
get
{
return _overlayMonitorIndex;
}
set
{
if (_overlayMonitorIndex != value)
{
_overlayMonitorIndex = value;
switch (_overlayMonitorIndex)
{
case 0:
Settings.Properties.OverlayMonitor.Value = "Main monitor";
RaisePropertyChanged();
break;
case 1:
Settings.Properties.OverlayMonitor.Value = "All monitors";
RaisePropertyChanged();
break;
}
}
}
}
public bool HideOverlayWhenUnmuted
{
get
{
return _hideOverlayWhenUnmuted;
}
set
{
if (value != _hideOverlayWhenUnmuted)
{
_hideOverlayWhenUnmuted = value;
Settings.Properties.HideOverlayWhenUnmuted.Value = value;
RaisePropertyChanged();
}
}
}
public string GetSettingsSubPath()
{
return _settingsConfigFileFolder + "\\" + ModuleName;
}
public void RaisePropertyChanged([CallerMemberName] string propertyName = null)
{
OnPropertyChanged(propertyName);
SndVideoConferenceSettings outsettings = new SndVideoConferenceSettings(Settings);
SndModuleSettings<SndVideoConferenceSettings> ipcMessage = new SndModuleSettings<SndVideoConferenceSettings>(outsettings);
SendConfigMSG(ipcMessage.ToJsonString());
}
}
[ComImport]
[Guid("3E68D4BD-7135-4D10-8018-9FB6D9F33FA1")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IInitializeWithWindow
{
void Initialize(IntPtr hwnd);
}
}

View file

@ -88,6 +88,13 @@
<FontIcon Glyph="&#xEDA7;"/>
</winui:NavigationViewItem.Icon>
</winui:NavigationViewItem>
<winui:NavigationViewItem x:Uid="Shell_VideoConference" helpers:NavHelper.NavigateTo="views:VideoConferencePage" IsEnabled="{x:Bind ViewModel.IsVideoConferenceBuild, Mode=OneWay}">
<winui:NavigationViewItem.Icon>
<FontIcon Glyph="&#xE714;"/>
</winui:NavigationViewItem.Icon>
</winui:NavigationViewItem>
</winui:NavigationView.MenuItems>
<i:Interaction.Behaviors>
<behaviors:NavigationViewHeaderBehavior
@ -116,4 +123,4 @@
</ScrollViewer>
</winui:NavigationView>
</Grid>
</UserControl>
</UserControl>

View file

@ -0,0 +1,156 @@
<Page
x:Class="Microsoft.PowerToys.Settings.UI.Views.VideoConferencePage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Microsoft.PowerToys.Settings.UI.Views"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:muxc="using:Microsoft.UI.Xaml.Controls"
xmlns:CustomControls="using:Microsoft.PowerToys.Settings.UI.Controls"
mc:Ignorable="d"
xmlns:ic="using:Microsoft.Xaml.Interactions.Core"
xmlns:i="using:Microsoft.Xaml.Interactivity"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid ColumnSpacing="{StaticResource DefaultColumnSpacing}" RowSpacing="{StaticResource DefaultRowSpacing}">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="LayoutVisualStates">
<VisualState x:Name="WideLayout">
<VisualState.StateTriggers>
<AdaptiveTrigger MinWindowWidth="{StaticResource WideLayoutMinWidth}" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="SidePanel.(Grid.Column)" Value="1" />
<Setter Target="SidePanel.(Grid.Row)" Value="0" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="SmallLayout">
<VisualState.StateTriggers>
<AdaptiveTrigger MinWindowWidth="{StaticResource SmallLayoutMinWidth}" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="SidePanel.(Grid.Column)" Value="0" />
<Setter Target="SidePanel.(Grid.Row)" Value="1" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<StackPanel Orientation="Vertical" x:Name="VideoConferenceView">
<TextBlock x:Uid="VideoConference_Description"
TextWrapping="Wrap"/>
<ToggleSwitch x:Uid="VideoConference_Enable"
IsOn="{ Binding Mode=TwoWay, Path=IsEnabled}"
Margin="{StaticResource MediumTopMargin}"/>
<CustomControls:HotkeySettingsControl
x:Uid="VideoConference_CameraAndMicrophoneMuteHotkeyControl_Header"
Width="320"
HorizontalAlignment="Left"
Margin="{StaticResource MediumTopMargin}"
HotkeySettings="{x:Bind Path=ViewModel.CameraAndMicrophoneMuteHotkey, Mode=TwoWay}"
IsEnabled="{ Binding Mode=TwoWay, Path=IsEnabled}"
/>
<CustomControls:HotkeySettingsControl
x:Uid="VideoConference_MicrophoneMuteHotkeyControl_Header"
Width="320"
HorizontalAlignment="Left"
Margin="{StaticResource MediumTopMargin}"
HotkeySettings="{x:Bind Path=ViewModel.MicrophoneMuteHotkey, Mode=TwoWay}"
IsEnabled="{ Binding Mode=TwoWay, Path=IsEnabled}"
/>
<CustomControls:HotkeySettingsControl
x:Uid="VideoConference_CameraMuteHotkeyControl_Header"
Width="320"
HorizontalAlignment="Left"
Margin="{StaticResource MediumTopMargin}"
HotkeySettings="{x:Bind Path=ViewModel.CameraMuteHotkey, Mode=TwoWay}"
IsEnabled="{ Binding Mode=TwoWay, Path=IsEnabled}"
/>
<ComboBox x:Uid="VideoConference_SelectedCamera"
Width="320"
HorizontalAlignment="Left"
Margin="{StaticResource MediumTopMargin}"
SelectedIndex="{Binding Path=SelectedCameraIndex, Mode=TwoWay}"
ItemsSource="{Binding CameraNames, Mode=OneTime}"
IsEnabled="{ Binding Mode=TwoWay, Path=IsEnabled}"/>
<TextBlock x:Uid="VideoConference_CameraOverlayImagePathHeader"
Margin="{StaticResource MediumTopMargin}"/>
<StackPanel Orientation="Horizontal" Padding="0" Spacing="4" Margin="{StaticResource XXSmallTopMargin}">
<Button Width="320"
Height="32"
IsEnabled="{ Binding Mode=TwoWay, Path=IsEnabled}"
Content="{Binding Mode=TwoWay, Path=CameraImageOverlayPath}"
Command="{Binding Mode=OneWay, Path=SelectOverlayImage}"
HorizontalContentAlignment="Left"
HorizontalAlignment="Left" />
<Button IsEnabled="{ Binding Mode=TwoWay, Path=IsEnabled}"
Height="32"
Content="Clear"
Command="{Binding Mode=OneWay, Path=ClearOverlayImage}"
HorizontalAlignment="Left" />
</StackPanel>
<ComboBox x:Uid="VideoConference_OverlayPosition"
MinWidth="240"
Margin="{StaticResource MediumTopMargin}"
SelectedIndex="{ Binding Mode=TwoWay, Path=OverlayPostionIndex}"
IsEnabled="{ Binding Mode=TwoWay, Path=IsEnabled}">
<ComboBoxItem x:Uid="VideoConference_OverlayPosition_TopLeftCorner"/>
<ComboBoxItem x:Uid="VideoConference_OverlayPosition_TopCenter"/>
<ComboBoxItem x:Uid="VideoConference_OverlayPosition_TopRightCorner"/>
<ComboBoxItem x:Uid="VideoConference_OverlayPosition_BottomLeftCorner"/>
<ComboBoxItem x:Uid="VideoConference_OverlayPosition_BottomCenter"/>
<ComboBoxItem x:Uid="VideoConference_OverlayPosition_BottomRightCorner"/>
</ComboBox>
<ComboBox x:Uid="VideoConference_OverlayMonitor"
MinWidth="240"
Margin="{StaticResource MediumTopMargin}"
SelectedIndex="{ Binding Mode=TwoWay, Path=OverlayMonitorIndex}"
IsEnabled="{ Binding Mode=TwoWay, Path=IsEnabled}">
<ComboBoxItem x:Uid="VideoConference_OverlayMonitor_Main"/>
<ComboBoxItem x:Uid="VideoConference_OverlayMonitor_All"/>
</ComboBox>
<CheckBox x:Uid="VideoConference_HideOverlayWhenUnmuted"
IsChecked="{x:Bind Mode=TwoWay, Path=ViewModel.HideOverlayWhenUnmuted}"
Margin="{StaticResource SmallTopMargin}"
IsEnabled="{x:Bind Mode=OneWay, Path=ViewModel.IsEnabled}"
/>
</StackPanel>
<StackPanel
x:Name="SidePanel"
Orientation="Vertical"
HorizontalAlignment="Left"
Width="{StaticResource SidePanelWidth}"
Grid.Column="1">
<TextBlock
x:Uid="About_VideoConference"
Style="{StaticResource SettingsGroupTitleStyle}"
Margin="{StaticResource XSmallBottomMargin}"/>
<HyperlinkButton NavigateUri="https://github.com/microsoft/PowerToys/blob/master/README.md">
<TextBlock x:Uid="Module_overview"/>
</HyperlinkButton>
<HyperlinkButton NavigateUri="https://github.com/microsoft/PowerToys/issues">
<TextBlock x:Uid="Give_Feedback" />
</HyperlinkButton>
</StackPanel>
</Grid>
</Page>

View file

@ -0,0 +1,22 @@
// 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 Microsoft.PowerToys.Settings.UI.ViewModels;
using Windows.UI.Xaml.Controls;
namespace Microsoft.PowerToys.Settings.UI.Views
{
public sealed partial class VideoConferencePage : Page
{
private VideoConferenceViewModel ViewModel { get; set; }
public VideoConferencePage()
{
InitializeComponent();
ViewModel = new VideoConferenceViewModel(ShellPage.SendDefaultIPCMessage);
DataContext = ViewModel;
}
}
}

15
src/hasWDK.props Normal file
View file

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Choose>
<When Condition="Exists('$(VCTargetsPath)\Microsoft.Cpp.WDK.props')">
<PropertyGroup>
<HasWDK>true</HasWDK>
</PropertyGroup>
</When>
<Otherwise>
<PropertyGroup>
<HasWDK>false</HasWDK>
</PropertyGroup>
</Otherwise>
</Choose>
</Project>

View file

@ -125,8 +125,6 @@ private:
size_t m_keyCycle{};
static const UINT m_showAnimationDuration = 200; // ms
static const UINT m_flashDuration = 700; // ms
ULONG_PTR gdiplusToken;
};
ZoneWindow::ZoneWindow(HINSTANCE hinstance)
@ -138,14 +136,10 @@ ZoneWindow::ZoneWindow(HINSTANCE hinstance)
wcex.lpszClassName = NonLocalizable::ToolWindowClassName;
wcex.hCursor = LoadCursorW(nullptr, IDC_ARROW);
RegisterClassExW(&wcex);
Gdiplus::GdiplusStartupInput gdiplusStartupInput;
Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
}
ZoneWindow::~ZoneWindow()
{
Gdiplus::GdiplusShutdown(gdiplusToken);
}
bool ZoneWindow::Init(IZoneWindowHost* host, HINSTANCE hinstance, HMONITOR monitor, const std::wstring& uniqueId, const std::wstring& parentUniqueId, bool flashZones)

View file

@ -0,0 +1,220 @@
#include <dxgiformat.h>
#include <assert.h>
#include <winrt/base.h>
#pragma warning(push)
#pragma warning(disable : 4005)
#include <wincodec.h>
#pragma warning(pop)
#include <memory>
#include <mfapi.h>
#include <shcore.h>
#include <algorithm>
#include <wil/resource.h>
#include "ImageLoader.h"
#include "Logging.h"
IWICImagingFactory* _GetWIC()
{
static IWICImagingFactory* s_Factory = nullptr;
if (s_Factory)
return s_Factory;
HRESULT hr = CoCreateInstance(
CLSID_WICImagingFactory,
nullptr,
CLSCTX_INPROC_SERVER,
__uuidof(IWICImagingFactory),
(LPVOID*)&s_Factory);
if (FAILED(hr))
{
LogToFile(std::string("_GetWIC() failed with code: " + hr));
s_Factory = nullptr;
return nullptr;
}
return s_Factory;
}
using Microsoft::WRL::ComPtr;
ComPtr<IMFSample> LoadImageAsSample(ComPtr<IStream> imageStream, IMFMediaType* sampleMediaType)
{
HRESULT hr = S_OK;
// Get target sample frame dimensions
UINT targetWidth = 0;
UINT targetHeight = 0;
RETURN_NULLPTR_IF_FAILED_WITH_LOGGING(MFGetAttributeSize(sampleMediaType, MF_MT_FRAME_SIZE, &targetWidth, &targetHeight));
IWICImagingFactory* pWIC = _GetWIC();
if (!pWIC)
{
LogToFile("Failed to create IWICImagingFactory");
return nullptr;
}
// Initialize image bitmap decoder from filename and get the image frame
ComPtr<IWICBitmapDecoder> bitmapDecoder;
RETURN_NULLPTR_IF_FAILED_WITH_LOGGING(pWIC->CreateDecoderFromStream(imageStream.Get(), nullptr, WICDecodeMetadataCacheOnLoad, &bitmapDecoder));
ComPtr<IWICBitmapFrameDecode> decodedFrame;
RETURN_NULLPTR_IF_FAILED_WITH_LOGGING(bitmapDecoder->GetFrame(0, &decodedFrame));
UINT imageWidth = 0, imageHeight = 0;
RETURN_NULLPTR_IF_FAILED_WITH_LOGGING(decodedFrame->GetSize(&imageWidth, &imageHeight));
// Scale the image if required
ComPtr<IWICBitmapSource> sourceImageFrame;
if (targetWidth != imageWidth || targetHeight != imageHeight)
{
ComPtr<IWICBitmapScaler> scaler;
RETURN_NULLPTR_IF_FAILED_WITH_LOGGING(pWIC->CreateBitmapScaler(&scaler));
RETURN_NULLPTR_IF_FAILED_WITH_LOGGING(scaler->Initialize(decodedFrame.Get(), targetWidth, targetHeight, WICBitmapInterpolationModeHighQualityCubic));
sourceImageFrame.Attach(scaler.Detach());
}
else
{
sourceImageFrame.Attach(decodedFrame.Detach());
}
MFT_REGISTER_TYPE_INFO outputFilter = { MFMediaType_Video, {} };
RETURN_NULLPTR_IF_FAILED_WITH_LOGGING(sampleMediaType->GetGUID(MF_MT_SUBTYPE, &outputFilter.guidSubtype));
ComPtr<IWICBitmapEncoder> jpgEncoder;
RETURN_NULLPTR_IF_FAILED_WITH_LOGGING(pWIC->CreateEncoder(
outputFilter.guidSubtype == MFVideoFormat_RGB24 ? GUID_ContainerFormatBmp : GUID_ContainerFormatJpeg, nullptr, &jpgEncoder));
// Prepare the encoder output memory stream and encoding params
ComPtr<IStream> jpgStream;
RETURN_NULLPTR_IF_FAILED_WITH_LOGGING(CreateStreamOnHGlobal(nullptr, true, &jpgStream));
RETURN_NULLPTR_IF_FAILED_WITH_LOGGING(jpgEncoder->Initialize(jpgStream.Get(), WICBitmapEncoderNoCache));
ComPtr<IWICBitmapFrameEncode> jpgFrame;
RETURN_NULLPTR_IF_FAILED_WITH_LOGGING(jpgEncoder->CreateNewFrame(&jpgFrame, nullptr));
RETURN_NULLPTR_IF_FAILED_WITH_LOGGING(jpgFrame->Initialize(nullptr));
WICPixelFormatGUID jpgFormat = GUID_WICPixelFormat24bppBGR;
RETURN_NULLPTR_IF_FAILED_WITH_LOGGING(jpgFrame->SetPixelFormat(&jpgFormat));
RETURN_NULLPTR_IF_FAILED_WITH_LOGGING(jpgFrame->SetSize(targetWidth, targetHeight));
// Commit the image encoding
RETURN_NULLPTR_IF_FAILED_WITH_LOGGING(jpgFrame->WriteSource(sourceImageFrame.Get(), nullptr));
RETURN_NULLPTR_IF_FAILED_WITH_LOGGING(jpgFrame->Commit());
RETURN_NULLPTR_IF_FAILED_WITH_LOGGING(jpgEncoder->Commit());
// Obtain stream size and lock its memory pointer
STATSTG jpgStreamStat{};
RETURN_NULLPTR_IF_FAILED_WITH_LOGGING(jpgStream->Stat(&jpgStreamStat, STATFLAG_NONAME));
const size_t jpgStreamSize = jpgStreamStat.cbSize.QuadPart;
HGLOBAL streamMemoryHandle{};
RETURN_NULLPTR_IF_FAILED_WITH_LOGGING(GetHGlobalFromStream(jpgStream.Get(), &streamMemoryHandle));
auto jpgStreamMemory = static_cast<uint8_t*>(GlobalLock(streamMemoryHandle));
auto unlockJpgStreamMemory = wil::scope_exit([jpgStreamMemory] { GlobalUnlock(jpgStreamMemory); });
// Create a sample from the input image buffer
ComPtr<IMFSample> inputSample;
RETURN_NULLPTR_IF_FAILED_WITH_LOGGING(MFCreateSample(&inputSample));
IMFMediaBuffer* inputMediaBuffer = nullptr;
RETURN_NULLPTR_IF_FAILED_WITH_LOGGING(MFCreateAlignedMemoryBuffer(static_cast<DWORD>(jpgStreamSize), MF_64_BYTE_ALIGNMENT, &inputMediaBuffer));
BYTE* inputBuf = nullptr;
DWORD max_length = 0, current_length = 0;
RETURN_NULLPTR_IF_FAILED_WITH_LOGGING(inputMediaBuffer->Lock(&inputBuf, &max_length, &current_length));
if (max_length < jpgStreamSize)
{
LogToFile("max_length < jpgStreamSize");
return nullptr;
}
std::copy(jpgStreamMemory, jpgStreamMemory + jpgStreamSize, inputBuf);
unlockJpgStreamMemory.reset();
RETURN_NULLPTR_IF_FAILED_WITH_LOGGING(inputMediaBuffer->Unlock());
RETURN_NULLPTR_IF_FAILED_WITH_LOGGING(inputMediaBuffer->SetCurrentLength(static_cast<DWORD>(jpgStreamSize)));
RETURN_NULLPTR_IF_FAILED_WITH_LOGGING(inputSample->AddBuffer(inputMediaBuffer));
// Now we are ready to convert it to the requested media type, so we need to find a suitable jpg encoder
MFT_REGISTER_TYPE_INFO inputFilter = { MFMediaType_Video, outputFilter.guidSubtype == MFVideoFormat_RGB24 ? MFVideoFormat_RGB24 : MFVideoFormat_MJPG };
// But if no conversion is needed, just return the input sample
if (!memcmp(&inputFilter.guidSubtype, &outputFilter.guidSubtype, sizeof(GUID)))
{
return inputSample;
}
IMFActivate** ppVDActivate = nullptr;
UINT32 count = 0;
RETURN_NULLPTR_IF_FAILED_WITH_LOGGING(MFTEnumEx(MFT_CATEGORY_VIDEO_DECODER, MFT_ENUM_FLAG_SYNCMFT, &inputFilter, &outputFilter, &ppVDActivate, &count));
ComPtr<IMFTransform> videoDecoder;
bool videoDecoderActivated = false;
for (UINT32 i = 0; i < count; ++i)
{
if (!videoDecoderActivated && !FAILED(ppVDActivate[i]->ActivateObject(IID_PPV_ARGS(&videoDecoder))))
{
videoDecoderActivated = true;
}
ppVDActivate[i]->Release();
}
if (count)
{
CoTaskMemFree(ppVDActivate);
}
if (!videoDecoderActivated)
{
LogToFile("No converter avialable for the selected format");
return nullptr;
}
auto shutdownVideoDecoder = wil::scope_exit([&videoDecoder] { MFShutdownObject(videoDecoder.Get()); });
// Set input/output types for the decoder
ComPtr<IMFMediaType> jpgFrameMediaType;
RETURN_NULLPTR_IF_FAILED_WITH_LOGGING(MFCreateMediaType(&jpgFrameMediaType));
RETURN_NULLPTR_IF_FAILED_WITH_LOGGING(jpgFrameMediaType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video));
RETURN_NULLPTR_IF_FAILED_WITH_LOGGING(jpgFrameMediaType->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_MJPG));
RETURN_NULLPTR_IF_FAILED_WITH_LOGGING(jpgFrameMediaType->SetUINT32(MF_MT_INTERLACE_MODE, MFVideoInterlace_Progressive));
RETURN_NULLPTR_IF_FAILED_WITH_LOGGING(jpgFrameMediaType->SetUINT32(MF_MT_ALL_SAMPLES_INDEPENDENT, TRUE));
RETURN_NULLPTR_IF_FAILED_WITH_LOGGING(MFSetAttributeSize(jpgFrameMediaType.Get(), MF_MT_FRAME_SIZE, targetWidth, targetHeight));
RETURN_NULLPTR_IF_FAILED_WITH_LOGGING(MFSetAttributeRatio(jpgFrameMediaType.Get(), MF_MT_PIXEL_ASPECT_RATIO, 1, 1));
RETURN_NULLPTR_IF_FAILED_WITH_LOGGING(videoDecoder->SetInputType(0, jpgFrameMediaType.Get(), 0));
RETURN_NULLPTR_IF_FAILED_WITH_LOGGING(videoDecoder->SetOutputType(0, sampleMediaType, 0));
// Process the input sample
RETURN_NULLPTR_IF_FAILED_WITH_LOGGING(videoDecoder->ProcessInput(0, inputSample.Get(), 0));
// Check whether we need to allocate output sample and buffer ourselves
MFT_OUTPUT_STREAM_INFO outputStreamInfo{};
RETURN_NULLPTR_IF_FAILED_WITH_LOGGING(videoDecoder->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)
{
RETURN_NULLPTR_IF_FAILED_WITH_LOGGING(MFCreateSample(&outputSample));
RETURN_NULLPTR_IF_FAILED_WITH_LOGGING(outputSample->SetSampleDuration(333333));
IMFMediaBuffer* outputMediaBuffer = nullptr;
RETURN_NULLPTR_IF_FAILED_WITH_LOGGING(MFCreateAlignedMemoryBuffer(outputStreamInfo.cbSize, outputStreamInfo.cbAlignment - 1, &outputMediaBuffer));
RETURN_NULLPTR_IF_FAILED_WITH_LOGGING(outputMediaBuffer->SetCurrentLength(outputStreamInfo.cbSize));
RETURN_NULLPTR_IF_FAILED_WITH_LOGGING(outputSample->AddBuffer(outputMediaBuffer));
outputSamples.pSample = outputSample;
}
// Finally, produce the output sample
DWORD processStatus = 0;
RETURN_NULLPTR_IF_FAILED_WITH_LOGGING(videoDecoder->ProcessOutput(0, 1, &outputSamples, &processStatus));
if (outputSamples.pEvents)
{
outputSamples.pEvents->Release();
}
return outputSamples.pSample;
}

View file

@ -0,0 +1,14 @@
#pragma once
#include <memory>
#include <optional>
#include <wrl/client.h>
#include <Mfidl.h>
#pragma warning(push)
#pragma warning(disable : 4005)
#include <stdint.h>
#pragma warning(pop)
Microsoft::WRL::ComPtr<IMFSample> LoadImageAsSample(Microsoft::WRL::ComPtr<IStream> imageStream, IMFMediaType* outputSampleMediaType);

View file

@ -0,0 +1,524 @@
#include "stdafx.h"
#include "SimpleMediaSource.h"
#include "SimpleMediaStream.h"
#include "Logging.h"
HRESULT
SimpleMediaSource::RuntimeClassInitialize()
{
VERBOSE_LOG;
HRESULT hr = S_OK;
RETURN_IF_FAILED_WITH_LOGGING(MFCreateAttributes(&_spAttributes, 10));
RETURN_IF_FAILED_WITH_LOGGING(MFCreateEventQueue(&_spEventQueue));
RETURN_IF_FAILED_WITH_LOGGING(MakeAndInitialize<SimpleMediaStream>(&_stream, this));
{
ComPtr<IMFStreamDescriptor> streamDescriptor(_stream.Get()->_spStreamDesc.Get());
RETURN_IF_FAILED_WITH_LOGGING(MFCreatePresentationDescriptor(1, streamDescriptor.GetAddressOf(), &_spPresentationDescriptor));
}
_wasStreamPreviouslySelected = false;
_sourceState = SourceState::Stopped;
return hr;
}
// IMFMediaEventGenerator methods.
IFACEMETHODIMP
SimpleMediaSource::BeginGetEvent(
_In_ IMFAsyncCallback* pCallback,
_In_ IUnknown* punkState)
{
HRESULT hr = S_OK;
auto lock = _critSec.Lock();
RETURN_IF_FAILED_WITH_LOGGING(_CheckShutdownRequiresLock());
RETURN_IF_FAILED_WITH_LOGGING(_spEventQueue->BeginGetEvent(pCallback, punkState));
return hr;
}
IFACEMETHODIMP
SimpleMediaSource::EndGetEvent(
_In_ IMFAsyncResult* pResult,
_COM_Outptr_ IMFMediaEvent** ppEvent)
{
HRESULT hr = S_OK;
auto lock = _critSec.Lock();
RETURN_IF_FAILED_WITH_LOGGING(_CheckShutdownRequiresLock());
RETURN_IF_FAILED_WITH_LOGGING(_spEventQueue->EndGetEvent(pResult, ppEvent));
return hr;
}
IFACEMETHODIMP
SimpleMediaSource::GetEvent(
DWORD dwFlags,
_COM_Outptr_ IMFMediaEvent** ppEvent)
{
// NOTE:
// GetEvent can block indefinitely, so we don't hold the lock.
// This requires some juggling with the event queue pointer.
HRESULT hr = S_OK;
ComPtr<IMFMediaEventQueue> spQueue;
{
auto lock = _critSec.Lock();
RETURN_IF_FAILED_WITH_LOGGING(_CheckShutdownRequiresLock());
spQueue = _spEventQueue;
}
// Now get the event.
RETURN_IF_FAILED_WITH_LOGGING(_spEventQueue->GetEvent(dwFlags, ppEvent));
return hr;
}
IFACEMETHODIMP
SimpleMediaSource::QueueEvent(
MediaEventType eventType,
REFGUID guidExtendedType,
HRESULT hrStatus,
_In_opt_ PROPVARIANT const* pvValue)
{
HRESULT hr = S_OK;
auto lock = _critSec.Lock();
RETURN_IF_FAILED_WITH_LOGGING(_CheckShutdownRequiresLock());
RETURN_IF_FAILED_WITH_LOGGING(_spEventQueue->QueueEventParamVar(eventType, guidExtendedType, hrStatus, pvValue));
return hr;
}
// IMFMediaSource methods
IFACEMETHODIMP
SimpleMediaSource::CreatePresentationDescriptor(
_COM_Outptr_ IMFPresentationDescriptor** ppPresentationDescriptor)
{
VERBOSE_LOG;
HRESULT hr = S_OK;
auto lock = _critSec.Lock();
if (ppPresentationDescriptor == nullptr)
{
return E_POINTER;
}
*ppPresentationDescriptor = nullptr;
RETURN_IF_FAILED_WITH_LOGGING(_CheckShutdownRequiresLock());
RETURN_IF_FAILED_WITH_LOGGING(_spPresentationDescriptor->Clone(ppPresentationDescriptor));
return hr;
}
IFACEMETHODIMP
SimpleMediaSource::GetCharacteristics(
_Out_ DWORD* pdwCharacteristics)
{
HRESULT hr = S_OK;
auto lock = _critSec.Lock();
if (nullptr == pdwCharacteristics)
{
return E_POINTER;
}
*pdwCharacteristics = 0;
RETURN_IF_FAILED_WITH_LOGGING(_CheckShutdownRequiresLock());
*pdwCharacteristics = MFMEDIASOURCE_IS_LIVE;
return hr;
}
IFACEMETHODIMP
SimpleMediaSource::Pause()
{
// Pause() not required/needed
return MF_E_INVALID_STATE_TRANSITION;
}
IFACEMETHODIMP
SimpleMediaSource::Shutdown()
{
VERBOSE_LOG;
HRESULT hr = S_OK;
auto lock = _critSec.Lock();
_sourceState = SourceState::Shutdown;
_spAttributes.Reset();
_spPresentationDescriptor.Reset();
if (_spEventQueue != nullptr)
{
_spEventQueue->Shutdown();
_spEventQueue.Reset();
}
if (_stream != nullptr)
{
_stream.Get()->Shutdown();
_stream.Reset();
}
return hr;
}
IFACEMETHODIMP
SimpleMediaSource::Start(
_In_ IMFPresentationDescriptor* pPresentationDescriptor,
_In_opt_ const GUID* pguidTimeFormat,
_In_ const PROPVARIANT* pvarStartPos)
{
VERBOSE_LOG;
HRESULT hr = S_OK;
auto lock = _critSec.Lock();
DWORD count = 0;
PROPVARIANT startTime;
BOOL selected = false;
ComPtr<IMFStreamDescriptor> streamDesc;
DWORD streamIndex = 0;
if (pPresentationDescriptor == nullptr || pvarStartPos == nullptr)
{
return E_INVALIDARG;
}
else if (pguidTimeFormat != nullptr && *pguidTimeFormat != GUID_NULL)
{
return MF_E_UNSUPPORTED_TIME_FORMAT;
}
RETURN_IF_FAILED_WITH_LOGGING(_CheckShutdownRequiresLock());
if (!(_sourceState != SourceState::Stopped || _sourceState != SourceState::Shutdown))
{
return MF_E_INVALID_STATE_TRANSITION;
}
_sourceState = SourceState::Started;
// This checks the passed in PresentationDescriptor matches the member of streams we
// have defined internally and that at least one stream is selected
RETURN_IF_FAILED_WITH_LOGGING(_ValidatePresentationDescriptor(pPresentationDescriptor));
RETURN_IF_FAILED_WITH_LOGGING(pPresentationDescriptor->GetStreamDescriptorCount(&count));
RETURN_IF_FAILED_WITH_LOGGING(InitPropVariantFromInt64(MFGetSystemTime(), &startTime));
// Send event that the source started. Include error code in case it failed.
RETURN_IF_FAILED_WITH_LOGGING(_spEventQueue->QueueEventParamVar(MESourceStarted,
GUID_NULL,
hr,
&startTime));
// We're hardcoding this to the first descriptor
// since this sample is a single stream sample. For
// multiple streams, we need to walk the list of streams
// and for each selected stream, send the MEUpdatedStream
// or MENewStream event along with the MEStreamStarted
// event.
RETURN_IF_FAILED_WITH_LOGGING(pPresentationDescriptor->GetStreamDescriptorByIndex(0,
&selected,
&streamDesc));
RETURN_IF_FAILED_WITH_LOGGING(streamDesc->GetStreamIdentifier(&streamIndex));
if (streamIndex >= 1)
{
return MF_E_INVALIDSTREAMNUMBER;
}
if (selected)
{
ComPtr<IUnknown> spunkStream;
MediaEventType met = (_wasStreamPreviouslySelected ? MEUpdatedStream : MENewStream);
// Update our internal PresentationDescriptor
RETURN_IF_FAILED_WITH_LOGGING(_spPresentationDescriptor->SelectStream(streamIndex));
RETURN_IF_FAILED_WITH_LOGGING(_stream.Get()->SetStreamState(MF_STREAM_STATE_RUNNING));
RETURN_IF_FAILED_WITH_LOGGING(_stream.As(&spunkStream));
// Send the MEUpdatedStream/MENewStream to our source event
// queue.
RETURN_IF_FAILED_WITH_LOGGING(_spEventQueue->QueueEventParamUnk(met,
GUID_NULL,
S_OK,
spunkStream.Get()));
// But for our stream started (MEStreamStarted), we post to our
// stream event queue.
RETURN_IF_FAILED_WITH_LOGGING(_stream.Get()->QueueEvent(MEStreamStarted,
GUID_NULL,
S_OK,
&startTime));
}
_wasStreamPreviouslySelected = selected;
return hr;
}
IFACEMETHODIMP
SimpleMediaSource::Stop()
{
VERBOSE_LOG;
HRESULT hr = S_OK;
auto lock = _critSec.Lock();
PROPVARIANT stopTime;
DWORD count = 0;
MF_STREAM_STATE state;
if (_sourceState != SourceState::Started)
{
return MF_E_INVALID_STATE_TRANSITION;
}
_sourceState = SourceState::Stopped;
RETURN_IF_FAILED_WITH_LOGGING(_CheckShutdownRequiresLock());
RETURN_IF_FAILED_WITH_LOGGING(InitPropVariantFromInt64(MFGetSystemTime(), &stopTime));
RETURN_IF_FAILED_WITH_LOGGING(_spPresentationDescriptor->GetStreamDescriptorCount(&count));
// Deselect the streams and send the stream stopped events.
RETURN_IF_FAILED_WITH_LOGGING(_stream.Get()->GetStreamState(&state));
_wasStreamPreviouslySelected = (state == MF_STREAM_STATE_RUNNING);
RETURN_IF_FAILED_WITH_LOGGING(_stream.Get()->SetStreamState(MF_STREAM_STATE_STOPPED));
_spPresentationDescriptor->DeselectStream(0);
RETURN_IF_FAILED_WITH_LOGGING(_stream.Get()->QueueEvent(MEStreamStopped, GUID_NULL, hr, &stopTime));
RETURN_IF_FAILED_WITH_LOGGING(_spEventQueue->QueueEventParamVar(MESourceStopped, GUID_NULL, hr, &stopTime));
return hr;
}
// IMFMediaSourceEx
IFACEMETHODIMP
SimpleMediaSource::GetSourceAttributes(
_COM_Outptr_ IMFAttributes** sourceAttributes)
{
HRESULT hr = S_OK;
auto lock = _critSec.Lock();
if (nullptr == sourceAttributes)
{
return E_POINTER;
}
RETURN_IF_FAILED_WITH_LOGGING(_CheckShutdownRequiresLock());
*sourceAttributes = nullptr;
if (_spAttributes.Get() == nullptr)
{
ComPtr<IMFSensorProfileCollection> profileCollection;
ComPtr<IMFSensorProfile> profile;
// Create our source attribute store.
RETURN_IF_FAILED_WITH_LOGGING(MFCreateAttributes(_spAttributes.GetAddressOf(), 1));
// Create an empty profile collection...
RETURN_IF_FAILED_WITH_LOGGING(MFCreateSensorProfileCollection(&profileCollection));
// In this example since we have just one stream, we only have one
// pin to add: Pin0.
// Legacy profile is mandatory. This is to ensure non-profile
// aware applications can still function, but with degraded
// feature sets.
RETURN_IF_FAILED_WITH_LOGGING(MFCreateSensorProfile(KSCAMERAPROFILE_Legacy, 0, nullptr, profile.ReleaseAndGetAddressOf()));
RETURN_IF_FAILED_WITH_LOGGING(profile->AddProfileFilter(0, L"((RES==;FRT<=30,1;SUT==))"));
RETURN_IF_FAILED_WITH_LOGGING(profileCollection->AddProfile(profile.Get()));
// High Frame Rate profile will only allow >=60fps.
RETURN_IF_FAILED_WITH_LOGGING(MFCreateSensorProfile(KSCAMERAPROFILE_HighFrameRate, 0, nullptr, profile.ReleaseAndGetAddressOf()));
RETURN_IF_FAILED_WITH_LOGGING(profile->AddProfileFilter(0, L"((RES==;FRT>=60,1;SUT==))"));
RETURN_IF_FAILED_WITH_LOGGING(profileCollection->AddProfile(profile.Get()));
// Se the profile collection to the attribute store of the IMFTransform.
RETURN_IF_FAILED_WITH_LOGGING(_spAttributes->SetUnknown(MF_DEVICEMFT_SENSORPROFILE_COLLECTION,
profileCollection.Get()));
}
return _spAttributes.CopyTo(sourceAttributes);
}
IFACEMETHODIMP
SimpleMediaSource::GetStreamAttributes(
DWORD dwStreamIdentifier,
_COM_Outptr_ IMFAttributes** ppAttributes)
{
HRESULT hr = S_OK;
auto lock = _critSec.Lock();
if (ppAttributes == nullptr)
{
return E_POINTER;
}
*ppAttributes = nullptr;
RETURN_IF_FAILED_WITH_LOGGING(_CheckShutdownRequiresLock());
if (dwStreamIdentifier >= 1)
{
return MF_E_INVALIDSTREAMNUMBER;
}
else
{
*ppAttributes = _stream.Get()->_spAttributes.Get();
(*ppAttributes)->AddRef();
}
return hr;
}
IFACEMETHODIMP
SimpleMediaSource::SetD3DManager(
_In_opt_ IUnknown* /*pManager*/
)
{
// Return code is ignored by the frame work, this is a
// best effort attempt to inform the media source of the
// DXGI manager to use if DX surface support is available.
return E_NOTIMPL;
}
struct __declspec(uuid("a1f58958-a5aa-412f-af20-1b7f1242dba0")) IMFDeviceController;
// IMFGetService methods
_Use_decl_annotations_
IFACEMETHODIMP
SimpleMediaSource::GetService(
_In_ REFGUID,
_In_ REFIID iid,
_Out_ LPVOID* ppvObject)
{
HRESULT hr = S_OK;
auto lock = _critSec.Lock();
const bool wantsIMFDeviceController = IsEqualIID(iid, __uuidof(IMFDeviceController));
auto stream = _stream.Get();
const bool goingToRestart = wantsIMFDeviceController && _wasStreamPreviouslySelected && stream && stream->_isSelected && _sourceState == SourceState::Started;
if (goingToRestart)
{
// GetService w /IMFDeviceController is called when we're already started -> stopping to prepare for restart
stream->SetStreamState(MF_STREAM_STATE_STOPPED);
return E_POINTER;
}
RETURN_IF_FAILED_WITH_LOGGING(_CheckShutdownRequiresLock());
if (!ppvObject)
{
return E_POINTER;
}
*ppvObject = nullptr;
// We have no supported service, just return
// MF_E_UNSUPPORTED_SERVICE for all calls.
return MF_E_UNSUPPORTED_SERVICE;
}
// IKsControl methods
_Use_decl_annotations_
IFACEMETHODIMP
SimpleMediaSource::KsProperty(
_In_reads_bytes_(ulPropertyLength) PKSPROPERTY,
_In_ ULONG,
_Inout_updates_to_(ulDataLength, *pBytesReturned) LPVOID,
_In_ ULONG,
_Out_ ULONG*)
{
// ERROR_SET_NOT_FOUND is the standard error code returned
// by the AV Stream driver framework when a miniport
// driver does not register a handler for a KS operation.
// We want to mimic the driver behavior here if we don't
// support controls.
return HRESULT_FROM_WIN32(ERROR_SET_NOT_FOUND);
}
_Use_decl_annotations_
IFACEMETHODIMP
SimpleMediaSource::KsMethod(
_In_reads_bytes_(ulMethodLength) PKSMETHOD,
_In_ ULONG,
_Inout_updates_to_(ulDataLength, *pBytesReturned) LPVOID,
_In_ ULONG,
_Out_ ULONG*)
{
Shutdown();
return HRESULT_FROM_WIN32(ERROR_SET_NOT_FOUND);
}
_Use_decl_annotations_
IFACEMETHODIMP
SimpleMediaSource::KsEvent(
_In_reads_bytes_opt_(ulEventLength) PKSEVENT,
_In_ ULONG,
_Inout_updates_to_(ulDataLength, *pBytesReturned) LPVOID,
_In_ ULONG,
_Out_opt_ ULONG*)
{
return HRESULT_FROM_WIN32(ERROR_SET_NOT_FOUND);
}
/// Internal methods.
HRESULT
SimpleMediaSource::_CheckShutdownRequiresLock()
{
if (_sourceState == SourceState::Shutdown)
{
return MF_E_SHUTDOWN;
}
if (_spEventQueue == nullptr || _stream == nullptr)
{
return E_UNEXPECTED;
}
return S_OK;
}
HRESULT
SimpleMediaSource::_ValidatePresentationDescriptor(
_In_ IMFPresentationDescriptor* pPD)
{
HRESULT hr = S_OK;
DWORD cStreams = 0;
bool anySelected = false;
if (pPD == nullptr)
{
return E_INVALIDARG;
}
// The caller's PD must have the same number of streams as ours.
RETURN_IF_FAILED_WITH_LOGGING(pPD->GetStreamDescriptorCount(&cStreams));
if (SUCCEEDED(hr) && (cStreams != 1))
{
return E_INVALIDARG;
}
// The caller must select at least one stream.
for (UINT32 i = 0; i < cStreams; ++i)
{
ComPtr<IMFStreamDescriptor> spSD;
BOOL fSelected = FALSE;
DWORD dwId = 0;
RETURN_IF_FAILED_WITH_LOGGING(pPD->GetStreamDescriptorByIndex(i, &fSelected, &spSD));
anySelected |= !!fSelected;
RETURN_IF_FAILED_WITH_LOGGING(spSD->GetStreamIdentifier(&dwId));
if (dwId >= 1)
{
return E_INVALIDARG;
}
}
if (!anySelected)
{
return E_INVALIDARG;
}
return hr;
}

View file

@ -0,0 +1,92 @@
#pragma once
#include "stdafx.h"
class SimpleMediaStream;
class __declspec(uuid("{8a6954dc-7baa-486b-a7a3-a3cc09246487}"))
SimpleMediaSource : public RuntimeClass<RuntimeClassFlags<WinRtClassicComMix>, IMFMediaEventGenerator, IMFMediaSource, IMFMediaSourceEx, IMFGetService, IKsControl>
{
enum class SourceState
{
Invalid,
Stopped,
Started,
Shutdown
};
public:
// IMFMediaEventGenerator
IFACEMETHOD(BeginGetEvent)
(_In_ IMFAsyncCallback* pCallback, _In_ IUnknown* punkState);
IFACEMETHOD(EndGetEvent)
(_In_ IMFAsyncResult* pResult, _Out_ IMFMediaEvent** ppEvent);
IFACEMETHOD(GetEvent)
(DWORD dwFlags, _Out_ IMFMediaEvent** ppEvent);
IFACEMETHOD(QueueEvent)
(MediaEventType met, REFGUID guidExtendedType, HRESULT hrStatus, _In_ const PROPVARIANT* pvValue);
// IMFMediaSource
IFACEMETHOD(CreatePresentationDescriptor)
(_Out_ IMFPresentationDescriptor** ppPresentationDescriptor);
IFACEMETHOD(GetCharacteristics)
(_Out_ DWORD* pdwCharacteristics);
IFACEMETHOD(Pause)
();
IFACEMETHOD(Shutdown)
();
IFACEMETHOD(Start)
(_In_ IMFPresentationDescriptor* pPresentationDescriptor, _In_ const GUID* pguidTimeFormat, _In_ const PROPVARIANT* pvarStartPosition);
IFACEMETHOD(Stop)
();
// IMFMediaSourceEx
IFACEMETHOD(GetSourceAttributes)
(_COM_Outptr_ IMFAttributes** ppAttributes);
IFACEMETHOD(GetStreamAttributes)
(DWORD dwStreamIdentifier, _COM_Outptr_ IMFAttributes** ppAttributes);
IFACEMETHOD(SetD3DManager)
(_In_opt_ IUnknown* pManager);
// IMFGetService
IFACEMETHOD(GetService)
(_In_ REFGUID guidService, _In_ REFIID riid, _Out_ LPVOID* ppvObject);
// IKsControl
IFACEMETHOD(KsProperty)
(_In_reads_bytes_(ulPropertyLength) PKSPROPERTY pProperty,
_In_ ULONG ulPropertyLength,
_Inout_updates_to_(ulDataLength, *pBytesReturned) LPVOID pPropertyData,
_In_ ULONG ulDataLength,
_Out_ ULONG* pBytesReturned);
IFACEMETHOD(KsMethod)
(_In_reads_bytes_(ulMethodLength) PKSMETHOD pMethod,
_In_ ULONG ulMethodLength,
_Inout_updates_to_(ulDataLength, *pBytesReturned) LPVOID pMethodData,
_In_ ULONG ulDataLength,
_Out_ ULONG* pBytesReturned);
IFACEMETHOD(KsEvent)
(_In_reads_bytes_opt_(ulEventLength) PKSEVENT pEvent,
_In_ ULONG ulEventLength,
_Inout_updates_to_(ulDataLength, *pBytesReturned) LPVOID pEventData,
_In_ ULONG ulDataLength,
_Out_opt_ ULONG* pBytesReturned);
public:
HRESULT RuntimeClassInitialize();
private:
HRESULT _CheckShutdownRequiresLock();
HRESULT _ValidatePresentationDescriptor(_In_ IMFPresentationDescriptor* pPresentationDescriptor);
CriticalSection _critSec;
SourceState _sourceState{ SourceState::Invalid };
ComPtr<IMFMediaEventQueue> _spEventQueue;
ComPtr<IMFPresentationDescriptor> _spPresentationDescriptor;
ComPtr<IMFAttributes> _spAttributes;
bool _wasStreamPreviouslySelected; // maybe makes more sense as a property of the stream
ComPtr<SimpleMediaStream> _stream;
};
CoCreatableClass(SimpleMediaSource);

View file

@ -0,0 +1,754 @@
#include "stdafx.h"
#include "ImageLoader.h"
#include <vector>
#include <algorithm>
#include <thread>
#include "SimpleMediaSource.h"
#include "SimpleMediaStream.h"
#include <common\user.h>
#include "Logging.h"
HRESULT CopyAttribute(IMFAttributes* pSrc, IMFAttributes* pDest, const GUID& key);
const static std::wstring_view MODULE_NAME = L"Video Conference";
const static std::wstring_view VIRTUAL_CAMERA_NAME = L"PowerToys VideoConference";
namespace
{
constexpr std::array<unsigned char, 3> blackColor = { 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, blackColor[0], blackColor[1], blackColor[2], 0x00
};
// clang-format on
}
void DeviceList::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 DeviceList::EnumerateDevices()
{
HRESULT hr = S_OK;
ComPtr<IMFAttributes> 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);
}
// Enumerate devices.
if (SUCCEEDED(hr))
{
hr = MFEnumDeviceSources(pAttributes.Get(), &m_ppDevices, &m_numberDevices);
}
if (FAILED(hr))
{
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 DeviceList::GetDevice(UINT32 index, IMFActivate** ppActivate)
{
if (index >= Count())
{
return E_INVALIDARG;
}
*ppActivate = m_ppDevices[index];
(*ppActivate)->AddRef();
return S_OK;
}
std::wstring_view DeviceList::GetDeviceName(UINT32 index)
{
if (index >= Count())
{
return {};
}
return m_deviceFriendlyNames[index];
}
//-------------------------------------------------------------------
// CopyAttribute
//
// Copy an attribute value from one attribute store to another.
//-------------------------------------------------------------------
HRESULT CopyAttribute(IMFAttributes* pSrc, IMFAttributes* pDest, const GUID& key)
{
PROPVARIANT var;
PropVariantInit(&var);
HRESULT hr = S_OK;
hr = pSrc->GetItem(key, &var);
if (SUCCEEDED(hr))
{
hr = pDest->SetItem(key, var);
}
PropVariantClear(&var);
return hr;
}
bool areSame(double lhs, double rhs)
{
const double EPSILON = 0.00000001;
return (fabs(lhs - rhs) < EPSILON);
}
ComPtr<IMFMediaType> SelectBestMediaType(IMFSourceReader* reader)
{
VERBOSE_LOG;
std::vector<ComPtr<IMFMediaType>> supportedMediaTypes;
auto typeFramerate = [](IMFMediaType* type) {
UINT32 framerateNum = 0, framerateDenum = 1;
MFGetAttributeRatio(type, MF_MT_FRAME_RATE, &framerateNum, &framerateDenum);
const float framerate = static_cast<float>(framerateNum) / framerateDenum;
return framerate;
};
bool is16by9RatioAvailable = false;
for (DWORD tyIdx = 0;; ++tyIdx)
{
IMFMediaType* nextType = nullptr;
HRESULT hr = reader->GetNativeMediaType((DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM, tyIdx, &nextType);
if (!nextType)
{
break;
}
UINT32 width = 0;
UINT32 height = 0;
MFGetAttributeSize(nextType, MF_MT_FRAME_SIZE, &width, &height);
double aspectRatio = static_cast<double>(width) / height;
GUID subtype{};
nextType->GetGUID(MF_MT_SUBTYPE, &subtype);
//LogToFile(std::string("Available format: ") +
// toMediaTypeString(subtype) +
// std::string(", width=") +
// std::to_string(width) +
// std::string(", height=") +
// std::to_string(height) +
// std::string(", aspect ratio=") +
// std::to_string(aspectRatio));
if (subtype != MFVideoFormat_YUY2 &&
subtype != MFVideoFormat_RGB24 &&
subtype != MFVideoFormat_MJPG &&
subtype != MFVideoFormat_NV12)
{
continue;
}
if (areSame(aspectRatio, 16. / 9.))
{
is16by9RatioAvailable = true;
}
constexpr float minimalAcceptableFramerate = 15.f;
// Skip low frame types
if (typeFramerate(nextType) < minimalAcceptableFramerate)
{
continue;
}
supportedMediaTypes.emplace_back(nextType);
if (hr == MF_E_NO_MORE_TYPES || FAILED(hr))
{
break;
}
}
if (is16by9RatioAvailable)
{
// Remove all types with non 16 by 9 ratio
supportedMediaTypes.erase(std::remove_if(begin(supportedMediaTypes), end(supportedMediaTypes), [](ComPtr<IMFMediaType>& ptr) {
UINT32 width = 0, height = 0;
MFGetAttributeSize(ptr.Get(), MF_MT_FRAME_SIZE, &width, &height);
double ratio = static_cast<double>(width) / height;
return !areSame(ratio, 16. / 9.);
}),
end(supportedMediaTypes));
}
UINT64 maxResolution = 0;
for (auto& type : supportedMediaTypes)
{
UINT32 width = 0;
UINT32 height = 0;
MFGetAttributeSize(type.Get(), MF_MT_FRAME_SIZE, &width, &height);
const UINT64 curResolutionMult = static_cast<UINT64>(width) * height;
if (curResolutionMult >= maxResolution)
{
maxResolution = curResolutionMult;
}
}
// Remove all types with non-optimal resolution
supportedMediaTypes.erase(std::remove_if(begin(supportedMediaTypes), end(supportedMediaTypes), [maxResolution](ComPtr<IMFMediaType>& ptr) {
UINT32 w = 0, h = 0;
MFGetAttributeSize(ptr.Get(), MF_MT_FRAME_SIZE, &w, &h);
const UINT64 curResolutionMult = static_cast<UINT64>(w) * h;
return curResolutionMult != maxResolution;
}),
end(supportedMediaTypes));
// Desc-sort by frame_rate
std::sort(begin(supportedMediaTypes), end(supportedMediaTypes), [typeFramerate](ComPtr<IMFMediaType>& lhs, ComPtr<IMFMediaType>& rhs) {
return typeFramerate(lhs.Get()) > typeFramerate(rhs.Get());
});
return std::move(supportedMediaTypes[0]);
}
HRESULT
SimpleMediaStream::RuntimeClassInitialize(
_In_ SimpleMediaSource* pSource)
{
VERBOSE_LOG;
HRESULT hr = S_OK;
if (nullptr == pSource)
{
return E_INVALIDARG;
}
RETURN_IF_FAILED_WITH_LOGGING(pSource->QueryInterface(IID_PPV_ARGS(&_parent)));
const auto newSettings = SyncCurrentSettings();
UpdateSourceCamera(newSettings.newCameraName);
ComPtr<IStream> blackBMPImage = SHCreateMemStream(bmpPixelData, sizeof(bmpPixelData));
if (!blackBMPImage || !_spMediaType)
{
return E_FAIL;
}
if (_spMediaType)
{
if (newSettings.overlayImage)
{
_overlayImage = LoadImageAsSample(newSettings.overlayImage, _spMediaType.Get());
}
_blackImage = LoadImageAsSample(blackBMPImage, _spMediaType.Get());
}
return S_OK;
}
// IMFMediaEventGenerator
IFACEMETHODIMP
SimpleMediaStream::BeginGetEvent(
_In_ IMFAsyncCallback* pCallback,
_In_ IUnknown* punkState)
{
HRESULT hr = S_OK;
auto lock = _critSec.Lock();
RETURN_IF_FAILED_WITH_LOGGING(_CheckShutdownRequiresLock());
RETURN_IF_FAILED_WITH_LOGGING(_spEventQueue->BeginGetEvent(pCallback, punkState));
return hr;
}
IFACEMETHODIMP
SimpleMediaStream::EndGetEvent(
_In_ IMFAsyncResult* pResult,
_COM_Outptr_ IMFMediaEvent** ppEvent)
{
HRESULT hr = S_OK;
auto lock = _critSec.Lock();
RETURN_IF_FAILED_WITH_LOGGING(_CheckShutdownRequiresLock());
RETURN_IF_FAILED_WITH_LOGGING(_spEventQueue->EndGetEvent(pResult, ppEvent));
return hr;
}
IFACEMETHODIMP
SimpleMediaStream::GetEvent(
DWORD dwFlags,
_COM_Outptr_ IMFMediaEvent** ppEvent)
{
// NOTE:
// GetEvent can block indefinitely, so we don't hold the lock.
// This requires some juggling with the event queue pointer.
HRESULT hr = S_OK;
ComPtr<IMFMediaEventQueue> spQueue;
{
auto lock = _critSec.Lock();
RETURN_IF_FAILED_WITH_LOGGING(_CheckShutdownRequiresLock());
spQueue = _spEventQueue;
}
// Now get the event.
RETURN_IF_FAILED_WITH_LOGGING(_spEventQueue->GetEvent(dwFlags, ppEvent));
return hr;
}
IFACEMETHODIMP
SimpleMediaStream::QueueEvent(
MediaEventType eventType,
REFGUID guidExtendedType,
HRESULT hrStatus,
_In_opt_ PROPVARIANT const* pvValue)
{
HRESULT hr = S_OK;
auto lock = _critSec.Lock();
RETURN_IF_FAILED_WITH_LOGGING(_CheckShutdownRequiresLock());
RETURN_IF_FAILED_WITH_LOGGING(_spEventQueue->QueueEventParamVar(eventType, guidExtendedType, hrStatus, pvValue));
return hr;
}
// IMFMediaStream
IFACEMETHODIMP
SimpleMediaStream::GetMediaSource(
_COM_Outptr_ IMFMediaSource** ppMediaSource)
{
HRESULT hr = S_OK;
auto lock = _critSec.Lock();
if (ppMediaSource == nullptr)
{
return E_POINTER;
}
*ppMediaSource = nullptr;
RETURN_IF_FAILED_WITH_LOGGING(_CheckShutdownRequiresLock());
*ppMediaSource = _parent.Get();
(*ppMediaSource)->AddRef();
return hr;
}
IFACEMETHODIMP
SimpleMediaStream::GetStreamDescriptor(
_COM_Outptr_ IMFStreamDescriptor** ppStreamDescriptor)
{
HRESULT hr = S_OK;
auto lock = _critSec.Lock();
if (ppStreamDescriptor == nullptr)
{
return E_POINTER;
}
*ppStreamDescriptor = nullptr;
RETURN_IF_FAILED_WITH_LOGGING(_CheckShutdownRequiresLock());
if (_spStreamDesc != nullptr)
{
*ppStreamDescriptor = _spStreamDesc.Get();
(*ppStreamDescriptor)->AddRef();
}
else
{
return E_UNEXPECTED;
}
return hr;
}
IFACEMETHODIMP
SimpleMediaStream::RequestSample(
_In_ IUnknown* pToken)
{
auto lock = _critSec.Lock();
HRESULT hr{};
RETURN_IF_FAILED_WITH_LOGGING(_CheckShutdownRequiresLock());
const auto syncedSettings = SyncCurrentSettings();
// Source camera is updated, we must shutdown ourselves, since we can't modify presentation descriptor while running
if (!syncedSettings.newCameraName.empty())
{
std::thread{ [this] {
auto lock = _critSec.Lock();
SetStreamState(MF_STREAM_STATE_STOPPED);
} }.detach();
}
else if (syncedSettings.overlayImage)
{
_overlayImage = LoadImageAsSample(syncedSettings.overlayImage, _spMediaType.Get());
}
// Request the first video frame.
ComPtr<IMFSample> sample;
DWORD streamFlags = 0;
const auto readSampleResult = _sourceCamera->ReadSample(
(DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM,
0,
nullptr,
&streamFlags,
nullptr,
&sample);
IMFSample* outputSample = syncedSettings.webcamDisabled ? _overlayImage.Get() : sample.Get();
const bool noSampleAvailable = !outputSample;
// use black image instead, it should be always available
if (noSampleAvailable)
{
outputSample = _blackImage.Get();
}
RETURN_IF_FAILED_WITH_LOGGING(outputSample->SetSampleTime(MFGetSystemTime()));
RETURN_IF_FAILED_WITH_LOGGING(outputSample->SetSampleDuration(333333));
if (pToken != nullptr)
{
RETURN_IF_FAILED_WITH_LOGGING(outputSample->SetUnknown(MFSampleExtension_Token, pToken));
}
if (FAILED(readSampleResult) && syncedSettings.newCameraName.empty())
{
// Try to reinit webcamera, since it could've been unavailable due to concurrent access from 3rd-party apps
UpdateSourceCamera(_currentSourceCameraName ? *_currentSourceCameraName : L"");
}
RETURN_IF_FAILED_WITH_LOGGING(_spEventQueue->QueueEventParamUnk(MEMediaSample,
GUID_NULL,
S_OK,
outputSample));
return S_OK;
}
//////////////////////////////////////////////////////////////////////////////////////////
// IMFMediaStream2
IFACEMETHODIMP
SimpleMediaStream::SetStreamState(
MF_STREAM_STATE state)
{
HRESULT hr = S_OK;
auto lock = _critSec.Lock();
bool runningState = false;
RETURN_IF_FAILED(_CheckShutdownRequiresLock());
switch (state)
{
case MF_STREAM_STATE_PAUSED:
goto done; // because not supported
case MF_STREAM_STATE_RUNNING:
runningState = true;
break;
case MF_STREAM_STATE_STOPPED:
runningState = false;
_parent->Shutdown();
break;
default:
hr = MF_E_INVALID_STATE_TRANSITION;
break;
}
_isSelected = runningState;
done:
return hr;
}
IFACEMETHODIMP
SimpleMediaStream::GetStreamState(
_Out_ MF_STREAM_STATE* pState)
{
HRESULT hr = S_OK;
auto lock = _critSec.Lock();
RETURN_IF_FAILED_WITH_LOGGING(_CheckShutdownRequiresLock());
*pState = (_isSelected ? MF_STREAM_STATE_RUNNING : MF_STREAM_STATE_STOPPED);
return hr;
}
HRESULT
SimpleMediaStream::Shutdown()
{
VERBOSE_LOG;
HRESULT hr = S_OK;
if (_settingsUpdateChannel.has_value())
{
_settingsUpdateChannel->access([this](auto settingsMemory) {
auto settings = reinterpret_cast<CameraSettingsUpdateChannel*>(settingsMemory._data);
settings->cameraInUse = false;
});
}
auto lock = _critSec.Lock();
_isShutdown = true;
_parent.Reset();
if (_spEventQueue != nullptr)
{
hr = _spEventQueue->Shutdown();
_spEventQueue.Reset();
}
_spAttributes.Reset();
_spMediaType.Reset();
_spStreamDesc.Reset();
_sourceCamera.Reset();
_currentSourceCameraName.reset();
_settingsUpdateChannel.reset();
_overlayImage.Reset();
_isSelected = false;
return hr;
}
HRESULT SimpleMediaStream::UpdateSourceCamera(std::wstring_view newCameraName)
{
VERBOSE_LOG;
HRESULT hr = S_OK;
_cameraList.Clear();
RETURN_IF_FAILED_WITH_LOGGING(_cameraList.EnumerateDevices());
bool webcamIsChosen = false;
ComPtr<IMFActivate> webcamSourceActivator;
for (UINT32 i = 0; i < _cameraList.Count(); ++i)
{
if (_cameraList.GetDeviceName(i) == newCameraName)
{
_cameraList.GetDevice(i, &webcamSourceActivator);
webcamIsChosen = true;
_currentSourceCameraName.emplace(newCameraName);
break;
}
}
// Try selecting the first camera which isn't us, since at this point we can only guess the user's preferrence
if (!webcamIsChosen)
{
for (UINT32 i = 0; i < _cameraList.Count(); ++i)
{
const auto camName = _cameraList.GetDeviceName(i);
const bool differentCamera = _currentSourceCameraName.has_value() && camName != *_currentSourceCameraName;
if (camName != VIRTUAL_CAMERA_NAME && (differentCamera || !_currentSourceCameraName.has_value()))
{
RETURN_IF_FAILED_WITH_LOGGING(_cameraList.GetDevice(i, &webcamSourceActivator));
webcamIsChosen = true;
_currentSourceCameraName.emplace(camName);
break;
}
}
}
if (!webcamIsChosen)
{
return E_ABORT;
}
ComPtr<IMFMediaSource> realSource;
RETURN_IF_FAILED_WITH_LOGGING(webcamSourceActivator->ActivateObject(
__uuidof(IMFMediaSource),
(void**)realSource.GetAddressOf()));
ComPtr<IMFAttributes> pAttributes;
hr = MFCreateAttributes(&pAttributes, 2);
if (SUCCEEDED(hr))
{
ComPtr<IMFMediaTypeHandler> spTypeHandler;
hr = MFCreateSourceReaderFromMediaSource(
realSource.Get(),
pAttributes.Get(),
&_sourceCamera);
_spAttributes.Reset();
_spMediaType = SelectBestMediaType(_sourceCamera.Get());
RETURN_IF_FAILED_WITH_LOGGING(MFCreateAttributes(&_spAttributes, 10));
RETURN_IF_FAILED_WITH_LOGGING(_SetStreamAttributes(_spAttributes.Get()));
if (_spEventQueue)
{
_spEventQueue->Shutdown();
_spEventQueue.Reset();
}
RETURN_IF_FAILED_WITH_LOGGING(MFCreateEventQueue(&_spEventQueue));
_spStreamDesc.Reset();
// Initialize stream descriptors
RETURN_IF_FAILED_WITH_LOGGING(MFCreateStreamDescriptor(0, 1, _spMediaType.GetAddressOf(), &_spStreamDesc));
RETURN_IF_FAILED_WITH_LOGGING(_spStreamDesc->GetMediaTypeHandler(&spTypeHandler));
RETURN_IF_FAILED_WITH_LOGGING(spTypeHandler->SetCurrentMediaType(_spMediaType.Get()));
RETURN_IF_FAILED_WITH_LOGGING(_SetStreamDescriptorAttributes(_spStreamDesc.Get()));
RETURN_IF_FAILED_WITH_LOGGING(_sourceCamera->SetCurrentMediaType((DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM, nullptr, _spMediaType.Get()));
}
return hr;
}
SimpleMediaStream::SyncedSettings SimpleMediaStream::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<CameraSettingsUpdateChannel*>(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<UINT>(imageMemory._size));
if (!result.overlayImage)
{
return;
}
settings->newOverlayImagePosted = false;
});
}
});
return result;
}
HRESULT
SimpleMediaStream::_CheckShutdownRequiresLock()
{
if (_isShutdown)
{
return MF_E_SHUTDOWN;
}
if (_spEventQueue == nullptr)
{
return E_UNEXPECTED;
}
return S_OK;
}
HRESULT
SimpleMediaStream::_SetStreamAttributes(
_In_ IMFAttributes* pAttributeStore)
{
HRESULT hr = S_OK;
if (nullptr == pAttributeStore)
{
return E_INVALIDARG;
}
RETURN_IF_FAILED_WITH_LOGGING(pAttributeStore->SetGUID(MF_DEVICESTREAM_STREAM_CATEGORY, PINNAME_VIDEO_CAPTURE));
RETURN_IF_FAILED_WITH_LOGGING(pAttributeStore->SetUINT32(MF_DEVICESTREAM_STREAM_ID, 0));
RETURN_IF_FAILED_WITH_LOGGING(pAttributeStore->SetUINT32(MF_DEVICESTREAM_FRAMESERVER_SHARED, 1));
RETURN_IF_FAILED_WITH_LOGGING(pAttributeStore->SetUINT32(MF_DEVICESTREAM_ATTRIBUTE_FRAMESOURCE_TYPES, _MFFrameSourceTypes::MFFrameSourceTypes_Color));
return hr;
}
HRESULT
SimpleMediaStream::_SetStreamDescriptorAttributes(
_In_ IMFAttributes* pAttributeStore)
{
HRESULT hr = S_OK;
if (nullptr == pAttributeStore)
{
return E_INVALIDARG;
}
RETURN_IF_FAILED_WITH_LOGGING(pAttributeStore->SetGUID(MF_DEVICESTREAM_STREAM_CATEGORY, PINNAME_VIDEO_CAPTURE));
RETURN_IF_FAILED_WITH_LOGGING(pAttributeStore->SetUINT32(MF_DEVICESTREAM_STREAM_ID, 0));
RETURN_IF_FAILED_WITH_LOGGING(pAttributeStore->SetUINT32(MF_DEVICESTREAM_FRAMESERVER_SHARED, 1));
RETURN_IF_FAILED_WITH_LOGGING(pAttributeStore->SetUINT32(MF_DEVICESTREAM_ATTRIBUTE_FRAMESOURCE_TYPES, _MFFrameSourceTypes::MFFrameSourceTypes_Color));
return hr;
}

View file

@ -0,0 +1,100 @@
#pragma once
#include "stdafx.h"
#include <SerializedSharedMemory.h>
#include <CameraStateUpdateChannels.h>
class SimpleMediaSource;
class DeviceList
{
UINT32 m_numberDevices;
IMFActivate** m_ppDevices = nullptr;
wchar_t** m_deviceFriendlyNames = nullptr;
public:
DeviceList() :
m_ppDevices(NULL), m_numberDevices(0)
{
}
~DeviceList()
{
Clear();
}
UINT32 Count() const { return m_numberDevices; }
void Clear();
HRESULT EnumerateDevices();
HRESULT GetDevice(UINT32 index, IMFActivate** ppActivate);
std::wstring_view GetDeviceName(UINT32 index);
};
class SimpleMediaStream : public RuntimeClass<RuntimeClassFlags<ClassicCom>, IMFMediaEventGenerator, IMFMediaStream, IMFMediaStream2>
{
friend class SimpleMediaSource;
public:
// IMFMediaEventGenerator
IFACEMETHOD(BeginGetEvent)
(IMFAsyncCallback* pCallback, IUnknown* punkState);
IFACEMETHOD(EndGetEvent)
(IMFAsyncResult* pResult, IMFMediaEvent** ppEvent);
IFACEMETHOD(GetEvent)
(DWORD dwFlags, IMFMediaEvent** ppEvent);
IFACEMETHOD(QueueEvent)
(MediaEventType met, REFGUID guidExtendedType, HRESULT hrStatus, const PROPVARIANT* pvValue);
// IMFMediaStream
IFACEMETHOD(GetMediaSource)
(IMFMediaSource** ppMediaSource);
IFACEMETHOD(GetStreamDescriptor)
(IMFStreamDescriptor** ppStreamDescriptor);
IFACEMETHOD(RequestSample)
(IUnknown* pToken);
// IMFMediaStream2
IFACEMETHOD(SetStreamState)
(MF_STREAM_STATE state);
IFACEMETHOD(GetStreamState)
(_Out_ MF_STREAM_STATE* pState);
// Non-interface methods.
HRESULT RuntimeClassInitialize(_In_ SimpleMediaSource* pSource);
HRESULT Shutdown();
protected:
struct SyncedSettings
{
bool webcamDisabled = false;
std::wstring newCameraName;
ComPtr<IStream> overlayImage;
};
HRESULT UpdateSourceCamera(std::wstring_view newCameraName);
SyncedSettings SyncCurrentSettings();
HRESULT _CheckShutdownRequiresLock();
HRESULT _SetStreamAttributes(IMFAttributes* pAttributeStore);
HRESULT _SetStreamDescriptorAttributes(IMFAttributes* pAttributeStore);
CriticalSection _critSec;
ComPtr<IMFMediaSource> _parent;
ComPtr<IMFMediaEventQueue> _spEventQueue;
ComPtr<IMFAttributes> _spAttributes;
ComPtr<IMFMediaType> _spMediaType;
ComPtr<IMFStreamDescriptor> _spStreamDesc;
bool _isShutdown = false;
bool _isSelected = false;
DeviceList _cameraList;
ComPtr<IMFSourceReader> _sourceCamera;
ComPtr<IMFSample> _overlayImage;
ComPtr<IMFSample> _blackImage;
std::optional<SerializedSharedMemory> _settingsUpdateChannel;
std::optional<std::wstring> _currentSourceCameraName;
};

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\..\..\hasWDK.props" />
<Import Project="WDK_VideoConferenceCustomMediaSource.vcxproj" Condition="$(HasWDK)==true" />
<Import Project="..\..\..\wdk_skip.vcxproj" Condition="$(HasWDK)==false" />
<PropertyGroup Label="Globals">
<IntDirSharingDetected>None</IntDirSharingDetected>
<ProjectGuid>{43AD9BF7-E765-48FE-9826-71A8F2CB12DD}</ProjectGuid>
</PropertyGroup>
</Project>

View file

@ -0,0 +1,133 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<VCProjectVersion>15.0</VCProjectVersion>
<ProjectGuid>{43AD9BF7-E765-48FE-9826-71A8F2CB12DD}</ProjectGuid>
<RootNamespace>VideoConference</RootNamespace>
<WindowsTargetPlatformVersion>10.0.18362.0</WindowsTargetPlatformVersion>
<ProjectName>VideoConferenceCustomMediaSource</ProjectName>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>WindowsApplicationForDrivers10.0</PlatformToolset>
<CharacterSet>MultiByte</CharacterSet>
<DriverTargetPlatform>Desktop</DriverTargetPlatform>
<Driver_SpectreMitigation>Spectre</Driver_SpectreMitigation>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>WindowsApplicationForDrivers10.0</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>MultiByte</CharacterSet>
<DriverTargetPlatform>Desktop</DriverTargetPlatform>
<Driver_SpectreMitigation>Spectre</Driver_SpectreMitigation>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<CodeAnalysisRuleSet>NativeRecommendedRules.ruleset</CodeAnalysisRuleSet>
<RunCodeAnalysis>false</RunCodeAnalysis>
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\modules\VideoConference\</OutDir>
<IntDir>$(SolutionDir)$(Platform)\$(Configuration)\obj\$(ProjectName)\</IntDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<CodeAnalysisRuleSet>NativeRecommendedRules.ruleset</CodeAnalysisRuleSet>
<RunCodeAnalysis>false</RunCodeAnalysis>
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\modules\VideoConference\</OutDir>
<IntDir>$(SolutionDir)$(Platform)\$(Configuration)\obj\$(ProjectName)\</IntDir>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<WarningLevel>Level4</WarningLevel>
<Optimization>Disabled</Optimization>
<SDLCheck>true</SDLCheck>
<ConformanceMode>true</ConformanceMode>
<EnablePREfast>false</EnablePREfast>
<ExceptionHandling>false</ExceptionHandling>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
<LanguageStandard>stdcpplatest</LanguageStandard>
<AdditionalIncludeDirectories>$(SolutionDir)\src\;$(IntDir);..\VideoConferenceShared\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType>
<TreatWarningAsError>true</TreatWarningAsError>
</ClCompile>
<Link>
<AdditionalDependencies>$(OutDir)..\..\common.lib;$(OutDir)VideoConferenceShared.lib;mfplat.lib;Mfsensorgroup.lib;%(AdditionalDependencies);OneCoreUAP.lib</AdditionalDependencies>
<ModuleDefinitionFile>$(ProjectDir)/module.def</ModuleDefinitionFile>
<IgnoreSpecificDefaultLibraries>%(IgnoreSpecificDefaultLibraries)</IgnoreSpecificDefaultLibraries>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<WarningLevel>Level4</WarningLevel>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<ConformanceMode>true</ConformanceMode>
<EnablePREfast>false</EnablePREfast>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<ExceptionHandling>Sync</ExceptionHandling>
<LanguageStandard>stdcpplatest</LanguageStandard>
<AdditionalIncludeDirectories>$(SolutionDir)\src\;$(IntDir);..\VideoConferenceShared\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType>
<TreatWarningAsError>true</TreatWarningAsError>
</ClCompile>
<Link>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<AdditionalDependencies>$(OutDir)..\..\common.lib;$(OutDir)VideoConferenceShared.lib;mfplat.lib;Mfsensorgroup.lib;%(AdditionalDependencies);OneCoreUAP.lib</AdditionalDependencies>
<IgnoreSpecificDefaultLibraries>%(IgnoreSpecificDefaultLibraries)</IgnoreSpecificDefaultLibraries>
<ModuleDefinitionFile>$(ProjectDir)/module.def</ModuleDefinitionFile>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<None Include="module.def" />
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="dllMain.cpp" />
<ClCompile Include="SimpleMediaSource.cpp" />
<ClCompile Include="SimpleMediaStream.cpp" />
<ClCompile Include="stdafx.cpp" />
<ClCompile Include="ImageLoader.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="SimpleMediaSource.h" />
<ClInclude Include="SimpleMediaStream.h" />
<ClInclude Include="stdafx.h" />
<ClInclude Include="ImageLoader.h" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
<Import Project="..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.200519.2\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.200519.2\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
</ImportGroup>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>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}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.200519.2\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.200519.2\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
</Target>
</Project>

View file

@ -0,0 +1,52 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="Header Files">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hh;hpp;hxx;hm;inl;inc;ipp;xsd</Extensions>
</Filter>
<Filter Include="Resource Files">
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
</Filter>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
<None Include="module.def" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="SimpleMediaSource.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="SimpleMediaStream.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="stdafx.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="dllMain.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="ImageLoader.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="SimpleMediaSource.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="SimpleMediaStream.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="ImageLoader.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="stdafx.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
</Project>

View file

@ -0,0 +1,35 @@
/// <copyright file="dllmain.cpp" company="Microsoft">
/// Copyright (c) Microsoft Corporation. All rights reserved.
/// </copyright>
// dllmain.cpp : Defines the entry point for the DLL application.
#include "stdafx.h"
#include <wrl\module.h>
#if !defined(__WRL_CLASSIC_COM__)
STDAPI DllGetActivationFactory(_In_ HSTRING activatibleClassId, _COM_Outptr_ IActivationFactory** factory)
{
return Module<InProc>::GetModule().GetActivationFactory(activatibleClassId, factory);
}
#endif
#if !defined(__WRL_WINRT_STRICT__)
STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, _COM_Outptr_ void** ppv)
{
return Module<InProc>::GetModule().GetClassObject(rclsid, riid, ppv);
}
#endif
STDAPI DllCanUnloadNow()
{
return Module<InProc>::GetModule().Terminate() ? S_OK : S_FALSE;
}
STDAPI_(BOOL) DllMain(_In_opt_ HINSTANCE hinst, DWORD reason, _In_opt_ void*)
{
if (reason == DLL_PROCESS_ATTACH)
{
DisableThreadLibraryCalls(hinst);
}
return TRUE;
}

View file

@ -0,0 +1,6 @@
LIBRARY
EXPORTS
DllGetActivationFactory PRIVATE
DllGetClassObject PRIVATE
DllCanUnloadNow PRIVATE

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.200519.2" targetFramework="native" />
</packages>

View file

@ -0,0 +1,8 @@
/// <copyright file="stdafx.cpp" company="Microsoft">
/// Copyright (c) Microsoft Corporation. All rights reserved.
/// </copyright>
// stdafx.cpp : source file that includes just the standard includes
// KinectMFMediaSource.pch will be the pre-compiled header
// stdafx.obj will contain the pre-compiled type information
#include "stdafx.h"

View file

@ -0,0 +1,75 @@
// stdafx.h : include file for standard system include files,
// or project specific include files that are used frequently, but
// are changed infrequently
//
#pragma once
#ifndef UNICODE
#define UNICODE
#endif
// Windows Header Files:
#include <windows.h>
#include <propvarutil.h>
//#include <mfstd.h> // Must be included before <initguid.h>, or else DirectDraw GUIDs will be defined twice. See the comment in <uuids.h>.
#include <ole2.h>
#include <initguid.h>
#include <ks.h>
#include <ksmedia.h>
#include <mfapi.h>
#include <mferror.h>
#include <mfidl.h>
#include <mfreadwrite.h>
#include <nserror.h>
#include <winmeta.h>
#include <wrl.h>
#include <d3d9types.h>
#include <new>
#include <windows.h>
#include <mfapi.h>
#include <mfidl.h>
#include <mfreadwrite.h>
#include <Wmcodecdsp.h>
#include <assert.h>
#include <Dbt.h>
#include <shlwapi.h>
#include <string_view>
#include <optional>
using namespace Microsoft::WRL;
using namespace Microsoft::WRL::Wrappers;
#if !defined(_IKsControl_)
#define _IKsControl_
interface DECLSPEC_UUID("28F54685-06FD-11D2-B27A-00A0C9223196") IKsControl;
#undef INTERFACE
#define INTERFACE IKsControl
DECLARE_INTERFACE_(IKsControl, IUnknown)
{
STDMETHOD(KsProperty)
(
THIS_
IN PKSPROPERTY Property,
IN ULONG PropertyLength,
IN OUT LPVOID PropertyData,
IN ULONG DataLength,
OUT ULONG * BytesReturned) PURE;
STDMETHOD(KsMethod)
(
THIS_
IN PKSMETHOD Method,
IN ULONG MethodLength,
IN OUT LPVOID MethodData,
IN ULONG DataLength,
OUT ULONG * BytesReturned) PURE;
STDMETHOD(KsEvent)
(
THIS_
IN PKSEVENT Event OPTIONAL,
IN ULONG EventLength,
IN OUT LPVOID EventData,
IN ULONG DataLength,
OUT ULONG * BytesReturned) PURE;
};
#endif // _IKsControl_

View file

@ -0,0 +1,39 @@
#include "pch.h"
#include "CVolumeNotification.h"
CVolumeNotification::CVolumeNotification(void) :
m_RefCount(1)
{
}
STDMETHODIMP_(ULONG __stdcall) CVolumeNotification::AddRef()
{
return InterlockedIncrement(&m_RefCount);
}
STDMETHODIMP_(ULONG __stdcall) CVolumeNotification::Release()
{
LONG ref = InterlockedDecrement(&m_RefCount);
if (ref == 0)
delete this;
return ref;
}
STDMETHODIMP_(HRESULT __stdcall) CVolumeNotification::QueryInterface(REFIID IID, void** ReturnValue)
{
if (IID == IID_IUnknown || IID == __uuidof(IAudioEndpointVolumeCallback))
{
*ReturnValue = static_cast<IUnknown*>(this);
AddRef();
return S_OK;
}
*ReturnValue = NULL;
return E_NOINTERFACE;
}
STDMETHODIMP_(HRESULT __stdcall) CVolumeNotification::OnNotify(PAUDIO_VOLUME_NOTIFICATION_DATA NotificationData)
{
Overlay::setMicrophoneMute(NotificationData->bMuted);
return S_OK;
}

View file

@ -0,0 +1,23 @@
#pragma once
#include <mmdeviceapi.h>
#include <endpointvolume.h>
#include "Overlay.h"
class CVolumeNotification : public IAudioEndpointVolumeCallback
{
public:
CVolumeNotification(void);
STDMETHODIMP_(ULONG)
AddRef();
STDMETHODIMP_(ULONG)
Release();
STDMETHODIMP QueryInterface(REFIID IID, void** ReturnValue);
STDMETHODIMP OnNotify(PAUDIO_VOLUME_NOTIFICATION_DATA NotificationData);
private:
~CVolumeNotification(void){};
LONG m_RefCount;
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

View file

@ -0,0 +1,19 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="322" height="44" viewBox="0 0 322 44">
<defs>
<clipPath id="clip-Off-Disabled">
<rect width="322" height="44"/>
</clipPath>
</defs>
<g id="Off-Disabled" clip-path="url(#clip-Off-Disabled)">
<rect width="322" height="44" fill="#f1f1f1"/>
<g id="Background" fill="#1f1f1f">
<path d="M 321.5 43.5 L 0.5 43.5 L 0.5 0.5 L 321.5 0.5 L 321.5 43.5 Z" stroke="none"/>
<path d="M 1 1 L 1 43 L 321 43 L 321 1 L 1 1 M 0 0 L 322 0 L 322 44 L 0 44 L 0 0 Z" stroke="none" fill="#313131"/>
</g>
<rect id="Divider" width="1" height="24" transform="translate(161 10)" fill="#313131"/>
<text id="Camera_label" data-name="Camera label" transform="translate(200 13)" fill="#585858" font-size="12" font-family="SegoeUI, Segoe UI"><tspan x="0" y="13">Camera not in use</tspan></text>
<path id="Camera_disabled_icon" data-name="Camera disabled icon" d="M12.5,509.031a3.408,3.408,0,0,1,1.359.272,3.634,3.634,0,0,1,1.117.745,3.348,3.348,0,0,1,.75,1.1A3.555,3.555,0,0,1,16,512.508a3.35,3.35,0,0,1-.273,1.351,3.608,3.608,0,0,1-.75,1.11,3.371,3.371,0,0,1-1.109.745,3.617,3.617,0,0,1-1.367.272,3.407,3.407,0,0,1-1.359-.272,3.634,3.634,0,0,1-1.117-.745,3.347,3.347,0,0,1-.75-1.1A3.555,3.555,0,0,1,9,512.508a3.351,3.351,0,0,1,.273-1.351,3.608,3.608,0,0,1,.75-1.11,3.371,3.371,0,0,1,1.109-.745A3.616,3.616,0,0,1,12.5,509.031ZM10,512.508a2.393,2.393,0,0,0,.2.963,2.585,2.585,0,0,0,.531.792,2.423,2.423,0,0,0,.8.536,2.491,2.491,0,0,0,.977.194,2.591,2.591,0,0,0,.719-.1,2.241,2.241,0,0,0,.656-.31l-3.461-3.439a2.334,2.334,0,0,0-.3.652A2.76,2.76,0,0,0,10,512.508Zm4.586,1.366a2.333,2.333,0,0,0,.3-.652,2.759,2.759,0,0,0,.109-.714,2.393,2.393,0,0,0-.2-.963,2.428,2.428,0,0,0-.539-.784,2.725,2.725,0,0,0-.8-.536,2.353,2.353,0,0,0-.969-.2,2.593,2.593,0,0,0-.719.1,2.245,2.245,0,0,0-.656.311ZM16,504v5.326a4.453,4.453,0,0,0-1-.823v-2.9l-3,1.5v.714a4.157,4.157,0,0,0-.508.078,4.1,4.1,0,0,0-.492.14v-2.981H1v5.962H8a4.038,4.038,0,0,0-.219.994H0v-7.95H12v1.925Z" transform="translate(171 -486)" fill="#585858"/>
<path id="Mic_off_icon" data-name="Mic off icon" d="M23.943,2.531l-1.019-1V1.5a1.448,1.448,0,0,1,.119-.586A1.5,1.5,0,0,1,23.37.437,1.538,1.538,0,0,1,24.453,0h4.076a1.538,1.538,0,0,1,1.083.437,1.5,1.5,0,0,1,.326.477,1.448,1.448,0,0,1,.119.586V8.531l-1.019-1V1.5a.472.472,0,0,0-.143-.352A.529.529,0,0,0,28.528,1H24.453a.491.491,0,0,0-.358.141.509.509,0,0,0-.151.359ZM32.095,8v2.523l-1.019-.992V8ZM35,15.148l-.716.7-3-2.945a3.218,3.218,0,0,1-1.106.8A3.388,3.388,0,0,1,28.831,14H27v1h2.038v1H23.943V15h2.038V14H24.15a3.206,3.206,0,0,1-1.266-.25,3.307,3.307,0,0,1-1.035-.687,3.239,3.239,0,0,1-.7-1.016,3.16,3.16,0,0,1-.263-1.25V8h1.019v2.8a2.093,2.093,0,0,0,.175.852,2.238,2.238,0,0,0,.486.7,2.2,2.2,0,0,0,.708.469,2.493,2.493,0,0,0,.876.18h4.681a2.187,2.187,0,0,0,.947-.211,2.427,2.427,0,0,0,.78-.586l-.812-.8a1.486,1.486,0,0,1-.533.438,1.613,1.613,0,0,1-.685.164H24.453a1.538,1.538,0,0,1-1.083-.437,1.5,1.5,0,0,1-.326-.477,1.448,1.448,0,0,1-.119-.586V4.711L19,.852l.716-.7ZM28.528,11a.5.5,0,0,0,.287-.086.526.526,0,0,0,.191-.234L23.943,5.711V10.5a.472.472,0,0,0,.143.352.529.529,0,0,0,.366.148Z" transform="translate(-9 14)" fill="#fff"/>
<text id="Mic_label" data-name="Mic label" transform="translate(38 13)" fill="#fff" font-size="12" font-family="SegoeUI, Segoe UI"><tspan x="0" y="13">Microphone off</tspan></text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

View file

@ -0,0 +1,19 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="322" height="44" viewBox="0 0 322 44">
<defs>
<clipPath id="clip-Off-Disabled_Light">
<rect width="322" height="44"/>
</clipPath>
</defs>
<g id="Off-Disabled_Light" data-name="Off-Disabled Light" clip-path="url(#clip-Off-Disabled_Light)">
<rect width="322" height="44" fill="#f1f1f1"/>
<g id="Background" fill="#f1f1f1">
<path d="M 321.5 43.5 L 0.5 43.5 L 0.5 0.5 L 321.5 0.5 L 321.5 43.5 Z" stroke="none"/>
<path d="M 1 1 L 1 43 L 321 43 L 321 1 L 1 1 M 0 0 L 322 0 L 322 44 L 0 44 L 0 0 Z" stroke="none" fill="#dbdbdb"/>
</g>
<rect id="Divider" width="1" height="24" transform="translate(161 10)" fill="#dbdbdb"/>
<text id="Camera_label" data-name="Camera label" transform="translate(200 13)" fill="#c1c1c1" font-size="12" font-family="SegoeUI, Segoe UI"><tspan x="0" y="13">Camera not in use</tspan></text>
<path id="Camera_disabled_icon" data-name="Camera disabled icon" d="M12.5,509.031a3.408,3.408,0,0,1,1.359.272,3.634,3.634,0,0,1,1.117.745,3.348,3.348,0,0,1,.75,1.1A3.555,3.555,0,0,1,16,512.508a3.35,3.35,0,0,1-.273,1.351,3.608,3.608,0,0,1-.75,1.11,3.371,3.371,0,0,1-1.109.745,3.617,3.617,0,0,1-1.367.272,3.407,3.407,0,0,1-1.359-.272,3.634,3.634,0,0,1-1.117-.745,3.347,3.347,0,0,1-.75-1.1A3.555,3.555,0,0,1,9,512.508a3.351,3.351,0,0,1,.273-1.351,3.608,3.608,0,0,1,.75-1.11,3.371,3.371,0,0,1,1.109-.745A3.616,3.616,0,0,1,12.5,509.031ZM10,512.508a2.393,2.393,0,0,0,.2.963,2.585,2.585,0,0,0,.531.792,2.423,2.423,0,0,0,.8.536,2.491,2.491,0,0,0,.977.194,2.591,2.591,0,0,0,.719-.1,2.241,2.241,0,0,0,.656-.31l-3.461-3.439a2.334,2.334,0,0,0-.3.652A2.76,2.76,0,0,0,10,512.508Zm4.586,1.366a2.333,2.333,0,0,0,.3-.652,2.759,2.759,0,0,0,.109-.714,2.393,2.393,0,0,0-.2-.963,2.428,2.428,0,0,0-.539-.784,2.725,2.725,0,0,0-.8-.536,2.353,2.353,0,0,0-.969-.2,2.593,2.593,0,0,0-.719.1,2.245,2.245,0,0,0-.656.311ZM16,504v5.326a4.453,4.453,0,0,0-1-.823v-2.9l-3,1.5v.714a4.157,4.157,0,0,0-.508.078,4.1,4.1,0,0,0-.492.14v-2.981H1v5.962H8a4.038,4.038,0,0,0-.219.994H0v-7.95H12v1.925Z" transform="translate(171 -486)" fill="#c1c1c1"/>
<path id="Mic_off_icon" data-name="Mic off icon" d="M23.943,2.531l-1.019-1V1.5a1.448,1.448,0,0,1,.119-.586A1.5,1.5,0,0,1,23.37.437,1.538,1.538,0,0,1,24.453,0h4.076a1.538,1.538,0,0,1,1.083.437,1.5,1.5,0,0,1,.326.477,1.448,1.448,0,0,1,.119.586V8.531l-1.019-1V1.5a.472.472,0,0,0-.143-.352A.529.529,0,0,0,28.528,1H24.453a.491.491,0,0,0-.358.141.509.509,0,0,0-.151.359ZM32.095,8v2.523l-1.019-.992V8ZM35,15.148l-.716.7-3-2.945a3.218,3.218,0,0,1-1.106.8A3.388,3.388,0,0,1,28.831,14H27v1h2.038v1H23.943V15h2.038V14H24.15a3.206,3.206,0,0,1-1.266-.25,3.307,3.307,0,0,1-1.035-.687,3.239,3.239,0,0,1-.7-1.016,3.16,3.16,0,0,1-.263-1.25V8h1.019v2.8a2.093,2.093,0,0,0,.175.852,2.238,2.238,0,0,0,.486.7,2.2,2.2,0,0,0,.708.469,2.493,2.493,0,0,0,.876.18h4.681a2.187,2.187,0,0,0,.947-.211,2.427,2.427,0,0,0,.78-.586l-.812-.8a1.486,1.486,0,0,1-.533.438,1.613,1.613,0,0,1-.685.164H24.453a1.538,1.538,0,0,1-1.083-.437,1.5,1.5,0,0,1-.326-.477,1.448,1.448,0,0,1-.119-.586V4.711L19,.852l.716-.7ZM28.528,11a.5.5,0,0,0,.287-.086.526.526,0,0,0,.191-.234L23.943,5.711V10.5a.472.472,0,0,0,.143.352.529.529,0,0,0,.366.148Z" transform="translate(-9 14)"/>
<text id="Mic_label" data-name="Mic label" transform="translate(38 13)" font-size="12" font-family="SegoeUI, Segoe UI"><tspan x="0" y="13">Microphone off</tspan></text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

View file

@ -0,0 +1,19 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="322" height="44" viewBox="0 0 322 44">
<defs>
<clipPath id="clip-Off-Off_Dark">
<rect width="322" height="44"/>
</clipPath>
</defs>
<g id="Off-Off_Dark" data-name="Off-Off Dark" clip-path="url(#clip-Off-Off_Dark)">
<rect width="322" height="44" fill="#f1f1f1"/>
<g id="Background" fill="#1f1f1f">
<path d="M 321.5 43.5 L 0.5 43.5 L 0.5 0.5 L 321.5 0.5 L 321.5 43.5 Z" stroke="none"/>
<path d="M 1 1 L 1 43 L 321 43 L 321 1 L 1 1 M 0 0 L 322 0 L 322 44 L 0 44 L 0 0 Z" stroke="none" fill="#313131"/>
</g>
<rect id="Divider" width="1" height="24" transform="translate(161 10)" fill="#313131"/>
<text id="Camera_label" data-name="Camera label" transform="translate(200 13)" fill="#fff" font-size="12" font-family="SegoeUI, Segoe UI"><tspan x="0" y="13">Camera off</tspan></text>
<path id="Camera_off_icon" data-name="Camera off icon" d="M12,24.9l4-2.038v8.279l-1.953-1q-.141-.072-.258-.119t-.242-.1a1.825,1.825,0,0,1-.227-.119l-.25-.159a1.726,1.726,0,0,1-.2-.159,1.556,1.556,0,0,1-.156-.167q-.07-.088-.148-.175l-.18-.2L11,27.533v-3.59H7.477l-1-1.019H12Zm3,4.593V24.508l-3,1.536v1.91ZM.148,19.716.852,19l15,15.284-.7.716-3.859-3.924H0V22.924H3.289ZM1,30.057h9.289l-6-6.113H1Z" transform="translate(171 -5)" fill="#fff"/>
<path id="Mic_off_icon" data-name="Mic off icon" d="M23.943,2.531l-1.019-1V1.5a1.448,1.448,0,0,1,.119-.586A1.5,1.5,0,0,1,23.37.437,1.538,1.538,0,0,1,24.453,0h4.076a1.538,1.538,0,0,1,1.083.437,1.5,1.5,0,0,1,.326.477,1.448,1.448,0,0,1,.119.586V8.531l-1.019-1V1.5a.472.472,0,0,0-.143-.352A.529.529,0,0,0,28.528,1H24.453a.491.491,0,0,0-.358.141.509.509,0,0,0-.151.359ZM32.095,8v2.523l-1.019-.992V8ZM35,15.148l-.716.7-3-2.945a3.218,3.218,0,0,1-1.106.8A3.388,3.388,0,0,1,28.831,14H27v1h2.038v1H23.943V15h2.038V14H24.15a3.206,3.206,0,0,1-1.266-.25,3.307,3.307,0,0,1-1.035-.687,3.239,3.239,0,0,1-.7-1.016,3.16,3.16,0,0,1-.263-1.25V8h1.019v2.8a2.093,2.093,0,0,0,.175.852,2.238,2.238,0,0,0,.486.7,2.2,2.2,0,0,0,.708.469,2.493,2.493,0,0,0,.876.18h4.681a2.187,2.187,0,0,0,.947-.211,2.427,2.427,0,0,0,.78-.586l-.812-.8a1.486,1.486,0,0,1-.533.438,1.613,1.613,0,0,1-.685.164H24.453a1.538,1.538,0,0,1-1.083-.437,1.5,1.5,0,0,1-.326-.477,1.448,1.448,0,0,1-.119-.586V4.711L19,.852l.716-.7ZM28.528,11a.5.5,0,0,0,.287-.086.526.526,0,0,0,.191-.234L23.943,5.711V10.5a.472.472,0,0,0,.143.352.529.529,0,0,0,.366.148Z" transform="translate(-9 14)" fill="#fff"/>
<text id="Mic_label" data-name="Mic label" transform="translate(38 13)" fill="#fff" font-size="12" font-family="SegoeUI, Segoe UI"><tspan x="0" y="13">Microphone off</tspan></text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

View file

@ -0,0 +1,19 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="322" height="44" viewBox="0 0 322 44">
<defs>
<clipPath id="clip-Off-Off_Light">
<rect width="322" height="44"/>
</clipPath>
</defs>
<g id="Off-Off_Light" data-name="Off-Off Light" clip-path="url(#clip-Off-Off_Light)">
<rect width="322" height="44" fill="#f1f1f1"/>
<g id="Background" fill="#f1f1f1">
<path d="M 321.5 43.5 L 0.5 43.5 L 0.5 0.5 L 321.5 0.5 L 321.5 43.5 Z" stroke="none"/>
<path d="M 1 1 L 1 43 L 321 43 L 321 1 L 1 1 M 0 0 L 322 0 L 322 44 L 0 44 L 0 0 Z" stroke="none" fill="#dbdbdb"/>
</g>
<rect id="Divider" width="1" height="24" transform="translate(161 10)" fill="#dbdbdb"/>
<text id="Camera_label" data-name="Camera label" transform="translate(200 13)" font-size="12" font-family="SegoeUI, Segoe UI"><tspan x="0" y="13">Camera off</tspan></text>
<path id="Camera_off_icon" data-name="Camera off icon" d="M12,24.9l4-2.038v8.279l-1.953-1q-.141-.072-.258-.119t-.242-.1a1.825,1.825,0,0,1-.227-.119l-.25-.159a1.726,1.726,0,0,1-.2-.159,1.556,1.556,0,0,1-.156-.167q-.07-.088-.148-.175l-.18-.2L11,27.533v-3.59H7.477l-1-1.019H12Zm3,4.593V24.508l-3,1.536v1.91ZM.148,19.716.852,19l15,15.284-.7.716-3.859-3.924H0V22.924H3.289ZM1,30.057h9.289l-6-6.113H1Z" transform="translate(171 -5)"/>
<path id="Mic_off_icon" data-name="Mic off icon" d="M23.943,2.531l-1.019-1V1.5a1.448,1.448,0,0,1,.119-.586A1.5,1.5,0,0,1,23.37.437,1.538,1.538,0,0,1,24.453,0h4.076a1.538,1.538,0,0,1,1.083.437,1.5,1.5,0,0,1,.326.477,1.448,1.448,0,0,1,.119.586V8.531l-1.019-1V1.5a.472.472,0,0,0-.143-.352A.529.529,0,0,0,28.528,1H24.453a.491.491,0,0,0-.358.141.509.509,0,0,0-.151.359ZM32.095,8v2.523l-1.019-.992V8ZM35,15.148l-.716.7-3-2.945a3.218,3.218,0,0,1-1.106.8A3.388,3.388,0,0,1,28.831,14H27v1h2.038v1H23.943V15h2.038V14H24.15a3.206,3.206,0,0,1-1.266-.25,3.307,3.307,0,0,1-1.035-.687,3.239,3.239,0,0,1-.7-1.016,3.16,3.16,0,0,1-.263-1.25V8h1.019v2.8a2.093,2.093,0,0,0,.175.852,2.238,2.238,0,0,0,.486.7,2.2,2.2,0,0,0,.708.469,2.493,2.493,0,0,0,.876.18h4.681a2.187,2.187,0,0,0,.947-.211,2.427,2.427,0,0,0,.78-.586l-.812-.8a1.486,1.486,0,0,1-.533.438,1.613,1.613,0,0,1-.685.164H24.453a1.538,1.538,0,0,1-1.083-.437,1.5,1.5,0,0,1-.326-.477,1.448,1.448,0,0,1-.119-.586V4.711L19,.852l.716-.7ZM28.528,11a.5.5,0,0,0,.287-.086.526.526,0,0,0,.191-.234L23.943,5.711V10.5a.472.472,0,0,0,.143.352.529.529,0,0,0,.366.148Z" transform="translate(-9 14)"/>
<text id="Mic_label" data-name="Mic label" transform="translate(38 13)" font-size="12" font-family="SegoeUI, Segoe UI"><tspan x="0" y="13">Microphone off</tspan></text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

View file

@ -0,0 +1,19 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="322" height="44" viewBox="0 0 322 44">
<defs>
<clipPath id="clip-Off-On_Dark">
<rect width="322" height="44"/>
</clipPath>
</defs>
<g id="Off-On_Dark" data-name="Off-On Dark" clip-path="url(#clip-Off-On_Dark)">
<rect width="322" height="44" fill="#f1f1f1"/>
<g id="Background" fill="#1f1f1f">
<path d="M 321.5 43.5 L 0.5 43.5 L 0.5 0.5 L 321.5 0.5 L 321.5 43.5 Z" stroke="none"/>
<path d="M 1 1 L 1 43 L 321 43 L 321 1 L 1 1 M 0 0 L 322 0 L 322 44 L 0 44 L 0 0 Z" stroke="none" fill="#313131"/>
</g>
<rect id="Divider" width="1" height="24" transform="translate(161 10)" fill="#313131"/>
<text id="Camera_label" data-name="Camera label" transform="translate(200 13)" fill="#fff" font-size="12" font-family="SegoeUI, Segoe UI"><tspan x="0" y="13">Camera on</tspan></text>
<path id="Mic_off_icon" data-name="Mic off icon" d="M23.943,2.531l-1.019-1V1.5a1.448,1.448,0,0,1,.119-.586A1.5,1.5,0,0,1,23.37.437,1.538,1.538,0,0,1,24.453,0h4.076a1.538,1.538,0,0,1,1.083.437,1.5,1.5,0,0,1,.326.477,1.448,1.448,0,0,1,.119.586V8.531l-1.019-1V1.5a.472.472,0,0,0-.143-.352A.529.529,0,0,0,28.528,1H24.453a.491.491,0,0,0-.358.141.509.509,0,0,0-.151.359ZM32.095,8v2.523l-1.019-.992V8ZM35,15.148l-.716.7-3-2.945a3.218,3.218,0,0,1-1.106.8A3.388,3.388,0,0,1,28.831,14H27v1h2.038v1H23.943V15h2.038V14H24.15a3.206,3.206,0,0,1-1.266-.25,3.307,3.307,0,0,1-1.035-.687,3.239,3.239,0,0,1-.7-1.016,3.16,3.16,0,0,1-.263-1.25V8h1.019v2.8a2.093,2.093,0,0,0,.175.852,2.238,2.238,0,0,0,.486.7,2.2,2.2,0,0,0,.708.469,2.493,2.493,0,0,0,.876.18h4.681a2.187,2.187,0,0,0,.947-.211,2.427,2.427,0,0,0,.78-.586l-.812-.8a1.486,1.486,0,0,1-.533.438,1.613,1.613,0,0,1-.685.164H24.453a1.538,1.538,0,0,1-1.083-.437,1.5,1.5,0,0,1-.326-.477,1.448,1.448,0,0,1-.119-.586V4.711L19,.852l.716-.7ZM28.528,11a.5.5,0,0,0,.287-.086.526.526,0,0,0,.191-.234L23.943,5.711V10.5a.472.472,0,0,0,.143.352.529.529,0,0,0,.366.148Z" transform="translate(-9 14)" fill="#fff"/>
<text id="Mic_label" data-name="Mic label" transform="translate(38 13)" fill="#fff" font-size="12" font-family="SegoeUI, Segoe UI"><tspan x="0" y="13">Microphone off</tspan></text>
<path id="Camera_on_icon" data-name="Camera on icon" d="M16,512.359,12,510.3V512.3H0v-8.231H12v1.993L16,504Zm-5-7.266H1v6.173H11Zm4,.571-3,1.551v1.929l3,1.551Z" transform="translate(171 -486)" fill="#fff"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

View file

@ -0,0 +1,19 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="322" height="44" viewBox="0 0 322 44">
<defs>
<clipPath id="clip-Off-On_Light">
<rect width="322" height="44"/>
</clipPath>
</defs>
<g id="Off-On_Light" data-name="Off-On Light" clip-path="url(#clip-Off-On_Light)">
<rect width="322" height="44" fill="#f1f1f1"/>
<g id="Background" fill="#f1f1f1">
<path d="M 321.5 43.5 L 0.5 43.5 L 0.5 0.5 L 321.5 0.5 L 321.5 43.5 Z" stroke="none"/>
<path d="M 1 1 L 1 43 L 321 43 L 321 1 L 1 1 M 0 0 L 322 0 L 322 44 L 0 44 L 0 0 Z" stroke="none" fill="#dbdbdb"/>
</g>
<rect id="Divider" width="1" height="24" transform="translate(161 10)" fill="#dbdbdb"/>
<text id="Camera_label" data-name="Camera label" transform="translate(200 13)" font-size="12" font-family="SegoeUI, Segoe UI"><tspan x="0" y="13">Camera on</tspan></text>
<path id="Mic_off_icon" data-name="Mic off icon" d="M23.943,2.531l-1.019-1V1.5a1.448,1.448,0,0,1,.119-.586A1.5,1.5,0,0,1,23.37.437,1.538,1.538,0,0,1,24.453,0h4.076a1.538,1.538,0,0,1,1.083.437,1.5,1.5,0,0,1,.326.477,1.448,1.448,0,0,1,.119.586V8.531l-1.019-1V1.5a.472.472,0,0,0-.143-.352A.529.529,0,0,0,28.528,1H24.453a.491.491,0,0,0-.358.141.509.509,0,0,0-.151.359ZM32.095,8v2.523l-1.019-.992V8ZM35,15.148l-.716.7-3-2.945a3.218,3.218,0,0,1-1.106.8A3.388,3.388,0,0,1,28.831,14H27v1h2.038v1H23.943V15h2.038V14H24.15a3.206,3.206,0,0,1-1.266-.25,3.307,3.307,0,0,1-1.035-.687,3.239,3.239,0,0,1-.7-1.016,3.16,3.16,0,0,1-.263-1.25V8h1.019v2.8a2.093,2.093,0,0,0,.175.852,2.238,2.238,0,0,0,.486.7,2.2,2.2,0,0,0,.708.469,2.493,2.493,0,0,0,.876.18h4.681a2.187,2.187,0,0,0,.947-.211,2.427,2.427,0,0,0,.78-.586l-.812-.8a1.486,1.486,0,0,1-.533.438,1.613,1.613,0,0,1-.685.164H24.453a1.538,1.538,0,0,1-1.083-.437,1.5,1.5,0,0,1-.326-.477,1.448,1.448,0,0,1-.119-.586V4.711L19,.852l.716-.7ZM28.528,11a.5.5,0,0,0,.287-.086.526.526,0,0,0,.191-.234L23.943,5.711V10.5a.472.472,0,0,0,.143.352.529.529,0,0,0,.366.148Z" transform="translate(-9 14)"/>
<text id="Mic_label" data-name="Mic label" transform="translate(38 13)" font-size="12" font-family="SegoeUI, Segoe UI"><tspan x="0" y="13">Microphone off</tspan></text>
<path id="Camera_on_icon" data-name="Camera on icon" d="M16,512.359,12,510.3V512.3H0v-8.231H12v1.993L16,504Zm-5-7.266H1v6.173H11Zm4,.571-3,1.551v1.929l3,1.551Z" transform="translate(171 -486)"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

View file

@ -0,0 +1,19 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="322" height="44" viewBox="0 0 322 44">
<defs>
<clipPath id="clip-On-Disabled">
<rect width="322" height="44"/>
</clipPath>
</defs>
<g id="On-Disabled" clip-path="url(#clip-On-Disabled)">
<rect width="322" height="44" fill="#f1f1f1"/>
<g id="Background" fill="#1f1f1f">
<path d="M 321.5 43.5 L 0.5 43.5 L 0.5 0.5 L 321.5 0.5 L 321.5 43.5 Z" stroke="none"/>
<path d="M 1 1 L 1 43 L 321 43 L 321 1 L 1 1 M 0 0 L 322 0 L 322 44 L 0 44 L 0 0 Z" stroke="none" fill="#1f1f1f"/>
</g>
<rect id="Divider" width="1" height="24" transform="translate(161 10)" fill="#313131"/>
<text id="Camera_label" data-name="Camera label" transform="translate(200 13)" fill="#585858" font-size="12" font-family="SegoeUI, Segoe UI"><tspan x="0" y="13">Camera not in use</tspan></text>
<path id="Camer_disabled_icon" data-name="Camer disabled icon" d="M12.5,509.031a3.408,3.408,0,0,1,1.359.272,3.634,3.634,0,0,1,1.117.745,3.348,3.348,0,0,1,.75,1.1A3.555,3.555,0,0,1,16,512.508a3.35,3.35,0,0,1-.273,1.351,3.608,3.608,0,0,1-.75,1.11,3.371,3.371,0,0,1-1.109.745,3.617,3.617,0,0,1-1.367.272,3.407,3.407,0,0,1-1.359-.272,3.634,3.634,0,0,1-1.117-.745,3.347,3.347,0,0,1-.75-1.1A3.555,3.555,0,0,1,9,512.508a3.351,3.351,0,0,1,.273-1.351,3.608,3.608,0,0,1,.75-1.11,3.371,3.371,0,0,1,1.109-.745A3.616,3.616,0,0,1,12.5,509.031ZM10,512.508a2.393,2.393,0,0,0,.2.963,2.585,2.585,0,0,0,.531.792,2.423,2.423,0,0,0,.8.536,2.491,2.491,0,0,0,.977.194,2.591,2.591,0,0,0,.719-.1,2.241,2.241,0,0,0,.656-.31l-3.461-3.439a2.334,2.334,0,0,0-.3.652A2.76,2.76,0,0,0,10,512.508Zm4.586,1.366a2.333,2.333,0,0,0,.3-.652,2.759,2.759,0,0,0,.109-.714,2.393,2.393,0,0,0-.2-.963,2.428,2.428,0,0,0-.539-.784,2.725,2.725,0,0,0-.8-.536,2.353,2.353,0,0,0-.969-.2,2.593,2.593,0,0,0-.719.1,2.245,2.245,0,0,0-.656.311ZM16,504v5.326a4.453,4.453,0,0,0-1-.823v-2.9l-3,1.5v.714a4.157,4.157,0,0,0-.508.078,4.1,4.1,0,0,0-.492.14v-2.981H1v5.962H8a4.038,4.038,0,0,0-.219.994H0v-7.95H12v1.925Z" transform="translate(171 -486)" fill="#585858"/>
<text id="Mic_label" data-name="Mic label" transform="translate(38 13)" fill="#fff" font-size="12" font-family="SegoeUI, Segoe UI"><tspan x="0" y="13">Microphone on</tspan></text>
<path id="Mic_on_icon" data-name="Mic on icon" d="M259.518,12.092a1.476,1.476,0,0,1-.589-.118,1.5,1.5,0,0,1-.8-.8,1.486,1.486,0,0,1-.118-.59V1.511a1.486,1.486,0,0,1,.118-.59,1.5,1.5,0,0,1,.8-.8A1.476,1.476,0,0,1,259.518,0h4.02a1.476,1.476,0,0,1,.589.118,1.5,1.5,0,0,1,.8.8,1.487,1.487,0,0,1,.118.59V10.58a1.487,1.487,0,0,1-.118.59,1.5,1.5,0,0,1-.8.8,1.476,1.476,0,0,1-.589.118Zm-.5-1.511a.51.51,0,0,0,.5.5h4.02a.51.51,0,0,0,.5-.5V1.511a.485.485,0,0,0-.149-.354.482.482,0,0,0-.353-.15h-4.02a.482.482,0,0,0-.353.15.485.485,0,0,0-.149.354Zm8.04-2.519v2.85a3.106,3.106,0,0,1-.251,1.244,3.2,3.2,0,0,1-1.7,1.7,3.085,3.085,0,0,1-1.241.252H262.03v1.008h2.01v1.008h-5.025V15.115h2.01V14.107h-1.837a3.085,3.085,0,0,1-1.241-.252,3.2,3.2,0,0,1-1.7-1.7A3.107,3.107,0,0,1,256,10.911V8.061h1.005v2.85a2.128,2.128,0,0,0,.173.85,2.2,2.2,0,0,0,.463.693,2.281,2.281,0,0,0,.7.472,1.981,1.981,0,0,0,.848.173h4.68a2.113,2.113,0,0,0,.848-.173,2.195,2.195,0,0,0,.691-.464,2.288,2.288,0,0,0,.471-.7,2,2,0,0,0,.173-.85V8.061Z" transform="translate(-244.073 14)" fill="#fff"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

View file

@ -0,0 +1,19 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="322" height="44" viewBox="0 0 322 44">
<defs>
<clipPath id="clip-On-Disabled_Light">
<rect width="322" height="44"/>
</clipPath>
</defs>
<g id="On-Disabled_Light" data-name="On-Disabled Light" clip-path="url(#clip-On-Disabled_Light)">
<rect width="322" height="44" fill="#f1f1f1"/>
<g id="Background" fill="#f1f1f1">
<path d="M 321.5 43.5 L 0.5 43.5 L 0.5 0.5 L 321.5 0.5 L 321.5 43.5 Z" stroke="none"/>
<path d="M 1 1 L 1 43 L 321 43 L 321 1 L 1 1 M 0 0 L 322 0 L 322 44 L 0 44 L 0 0 Z" stroke="none" fill="#dbdbdb"/>
</g>
<rect id="Divider" width="1" height="24" transform="translate(161 10)" fill="#dbdbdb"/>
<text id="Camera_label" data-name="Camera label" transform="translate(200 13)" fill="#c1c1c1" font-size="12" font-family="SegoeUI, Segoe UI"><tspan x="0" y="13">Camera not in use</tspan></text>
<path id="Camer_disabled_icon" data-name="Camer disabled icon" d="M12.5,509.031a3.408,3.408,0,0,1,1.359.272,3.634,3.634,0,0,1,1.117.745,3.348,3.348,0,0,1,.75,1.1A3.555,3.555,0,0,1,16,512.508a3.35,3.35,0,0,1-.273,1.351,3.608,3.608,0,0,1-.75,1.11,3.371,3.371,0,0,1-1.109.745,3.617,3.617,0,0,1-1.367.272,3.407,3.407,0,0,1-1.359-.272,3.634,3.634,0,0,1-1.117-.745,3.347,3.347,0,0,1-.75-1.1A3.555,3.555,0,0,1,9,512.508a3.351,3.351,0,0,1,.273-1.351,3.608,3.608,0,0,1,.75-1.11,3.371,3.371,0,0,1,1.109-.745A3.616,3.616,0,0,1,12.5,509.031ZM10,512.508a2.393,2.393,0,0,0,.2.963,2.585,2.585,0,0,0,.531.792,2.423,2.423,0,0,0,.8.536,2.491,2.491,0,0,0,.977.194,2.591,2.591,0,0,0,.719-.1,2.241,2.241,0,0,0,.656-.31l-3.461-3.439a2.334,2.334,0,0,0-.3.652A2.76,2.76,0,0,0,10,512.508Zm4.586,1.366a2.333,2.333,0,0,0,.3-.652,2.759,2.759,0,0,0,.109-.714,2.393,2.393,0,0,0-.2-.963,2.428,2.428,0,0,0-.539-.784,2.725,2.725,0,0,0-.8-.536,2.353,2.353,0,0,0-.969-.2,2.593,2.593,0,0,0-.719.1,2.245,2.245,0,0,0-.656.311ZM16,504v5.326a4.453,4.453,0,0,0-1-.823v-2.9l-3,1.5v.714a4.157,4.157,0,0,0-.508.078,4.1,4.1,0,0,0-.492.14v-2.981H1v5.962H8a4.038,4.038,0,0,0-.219.994H0v-7.95H12v1.925Z" transform="translate(171 -486)" fill="#c1c1c1"/>
<text id="Mic_label" data-name="Mic label" transform="translate(38 13)" font-size="12" font-family="SegoeUI, Segoe UI"><tspan x="0" y="13">Microphone on</tspan></text>
<path id="Mic_on_icon" data-name="Mic on icon" d="M259.518,12.092a1.476,1.476,0,0,1-.589-.118,1.5,1.5,0,0,1-.8-.8,1.486,1.486,0,0,1-.118-.59V1.511a1.486,1.486,0,0,1,.118-.59,1.5,1.5,0,0,1,.8-.8A1.476,1.476,0,0,1,259.518,0h4.02a1.476,1.476,0,0,1,.589.118,1.5,1.5,0,0,1,.8.8,1.487,1.487,0,0,1,.118.59V10.58a1.487,1.487,0,0,1-.118.59,1.5,1.5,0,0,1-.8.8,1.476,1.476,0,0,1-.589.118Zm-.5-1.511a.51.51,0,0,0,.5.5h4.02a.51.51,0,0,0,.5-.5V1.511a.485.485,0,0,0-.149-.354.482.482,0,0,0-.353-.15h-4.02a.482.482,0,0,0-.353.15.485.485,0,0,0-.149.354Zm8.04-2.519v2.85a3.106,3.106,0,0,1-.251,1.244,3.2,3.2,0,0,1-1.7,1.7,3.085,3.085,0,0,1-1.241.252H262.03v1.008h2.01v1.008h-5.025V15.115h2.01V14.107h-1.837a3.085,3.085,0,0,1-1.241-.252,3.2,3.2,0,0,1-1.7-1.7A3.107,3.107,0,0,1,256,10.911V8.061h1.005v2.85a2.128,2.128,0,0,0,.173.85,2.2,2.2,0,0,0,.463.693,2.281,2.281,0,0,0,.7.472,1.981,1.981,0,0,0,.848.173h4.68a2.113,2.113,0,0,0,.848-.173,2.195,2.195,0,0,0,.691-.464,2.288,2.288,0,0,0,.471-.7,2,2,0,0,0,.173-.85V8.061Z" transform="translate(-244.073 14)"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

View file

@ -0,0 +1,19 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="322" height="44" viewBox="0 0 322 44">
<defs>
<clipPath id="clip-On-Off_Dark">
<rect width="322" height="44"/>
</clipPath>
</defs>
<g id="On-Off_Dark" data-name="On-Off Dark" clip-path="url(#clip-On-Off_Dark)">
<rect width="322" height="44" fill="#f1f1f1"/>
<g id="Background" fill="#1f1f1f">
<path d="M 321.5 43.5 L 0.5 43.5 L 0.5 0.5 L 321.5 0.5 L 321.5 43.5 Z" stroke="none"/>
<path d="M 1 1 L 1 43 L 321 43 L 321 1 L 1 1 M 0 0 L 322 0 L 322 44 L 0 44 L 0 0 Z" stroke="none" fill="#313131"/>
</g>
<rect id="Divider" width="1" height="24" transform="translate(161 10)" fill="#313131"/>
<text id="Camera_label" data-name="Camera label" transform="translate(200 13)" fill="#fff" font-size="12" font-family="SegoeUI, Segoe UI"><tspan x="0" y="13">Camera off</tspan></text>
<path id="Camera_off_icon" data-name="Camera off icon" d="M12,24.9l4-2.038v8.279l-1.953-1q-.141-.072-.258-.119t-.242-.1a1.825,1.825,0,0,1-.227-.119l-.25-.159a1.726,1.726,0,0,1-.2-.159,1.556,1.556,0,0,1-.156-.167q-.07-.088-.148-.175l-.18-.2L11,27.533v-3.59H7.477l-1-1.019H12Zm3,4.593V24.508l-3,1.536v1.91ZM.148,19.716.852,19l15,15.284-.7.716-3.859-3.924H0V22.924H3.289ZM1,30.057h9.289l-6-6.113H1Z" transform="translate(171 -5)" fill="#fff"/>
<text id="Mic_label" data-name="Mic label" transform="translate(38 13)" fill="#fff" font-size="12" font-family="SegoeUI, Segoe UI"><tspan x="0" y="13">Microphone on</tspan></text>
<path id="Mic_on_icon" data-name="Mic on icon" d="M259.518,12.092a1.476,1.476,0,0,1-.589-.118,1.5,1.5,0,0,1-.8-.8,1.486,1.486,0,0,1-.118-.59V1.511a1.486,1.486,0,0,1,.118-.59,1.5,1.5,0,0,1,.8-.8A1.476,1.476,0,0,1,259.518,0h4.02a1.476,1.476,0,0,1,.589.118,1.5,1.5,0,0,1,.8.8,1.487,1.487,0,0,1,.118.59V10.58a1.487,1.487,0,0,1-.118.59,1.5,1.5,0,0,1-.8.8,1.476,1.476,0,0,1-.589.118Zm-.5-1.511a.51.51,0,0,0,.5.5h4.02a.51.51,0,0,0,.5-.5V1.511a.485.485,0,0,0-.149-.354.482.482,0,0,0-.353-.15h-4.02a.482.482,0,0,0-.353.15.485.485,0,0,0-.149.354Zm8.04-2.519v2.85a3.106,3.106,0,0,1-.251,1.244,3.2,3.2,0,0,1-1.7,1.7,3.085,3.085,0,0,1-1.241.252H262.03v1.008h2.01v1.008h-5.025V15.115h2.01V14.107h-1.837a3.085,3.085,0,0,1-1.241-.252,3.2,3.2,0,0,1-1.7-1.7A3.107,3.107,0,0,1,256,10.911V8.061h1.005v2.85a2.128,2.128,0,0,0,.173.85,2.2,2.2,0,0,0,.463.693,2.281,2.281,0,0,0,.7.472,1.981,1.981,0,0,0,.848.173h4.68a2.113,2.113,0,0,0,.848-.173,2.195,2.195,0,0,0,.691-.464,2.288,2.288,0,0,0,.471-.7,2,2,0,0,0,.173-.85V8.061Z" transform="translate(-244.073 14)" fill="#fff"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

View file

@ -0,0 +1,19 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="322" height="44" viewBox="0 0 322 44">
<defs>
<clipPath id="clip-On-Off_Light">
<rect width="322" height="44"/>
</clipPath>
</defs>
<g id="On-Off_Light" data-name="On-Off Light" clip-path="url(#clip-On-Off_Light)">
<rect width="322" height="44" fill="#f1f1f1"/>
<g id="Background" fill="#f1f1f1">
<path d="M 321.5 43.5 L 0.5 43.5 L 0.5 0.5 L 321.5 0.5 L 321.5 43.5 Z" stroke="none"/>
<path d="M 1 1 L 1 43 L 321 43 L 321 1 L 1 1 M 0 0 L 322 0 L 322 44 L 0 44 L 0 0 Z" stroke="none" fill="#dbdbdb"/>
</g>
<rect id="Divider" width="1" height="24" transform="translate(161 10)" fill="#dbdbdb"/>
<text id="Camera_label" data-name="Camera label" transform="translate(200 13)" font-size="12" font-family="SegoeUI, Segoe UI"><tspan x="0" y="13">Camera off</tspan></text>
<path id="Camera_off_icon" data-name="Camera off icon" d="M12,24.9l4-2.038v8.279l-1.953-1q-.141-.072-.258-.119t-.242-.1a1.825,1.825,0,0,1-.227-.119l-.25-.159a1.726,1.726,0,0,1-.2-.159,1.556,1.556,0,0,1-.156-.167q-.07-.088-.148-.175l-.18-.2L11,27.533v-3.59H7.477l-1-1.019H12Zm3,4.593V24.508l-3,1.536v1.91ZM.148,19.716.852,19l15,15.284-.7.716-3.859-3.924H0V22.924H3.289ZM1,30.057h9.289l-6-6.113H1Z" transform="translate(171 -5)"/>
<text id="Mic_label" data-name="Mic label" transform="translate(38 13)" font-size="12" font-family="SegoeUI, Segoe UI"><tspan x="0" y="13">Microphone on</tspan></text>
<path id="Mic_on_icon" data-name="Mic on icon" d="M259.518,12.092a1.476,1.476,0,0,1-.589-.118,1.5,1.5,0,0,1-.8-.8,1.486,1.486,0,0,1-.118-.59V1.511a1.486,1.486,0,0,1,.118-.59,1.5,1.5,0,0,1,.8-.8A1.476,1.476,0,0,1,259.518,0h4.02a1.476,1.476,0,0,1,.589.118,1.5,1.5,0,0,1,.8.8,1.487,1.487,0,0,1,.118.59V10.58a1.487,1.487,0,0,1-.118.59,1.5,1.5,0,0,1-.8.8,1.476,1.476,0,0,1-.589.118Zm-.5-1.511a.51.51,0,0,0,.5.5h4.02a.51.51,0,0,0,.5-.5V1.511a.485.485,0,0,0-.149-.354.482.482,0,0,0-.353-.15h-4.02a.482.482,0,0,0-.353.15.485.485,0,0,0-.149.354Zm8.04-2.519v2.85a3.106,3.106,0,0,1-.251,1.244,3.2,3.2,0,0,1-1.7,1.7,3.085,3.085,0,0,1-1.241.252H262.03v1.008h2.01v1.008h-5.025V15.115h2.01V14.107h-1.837a3.085,3.085,0,0,1-1.241-.252,3.2,3.2,0,0,1-1.7-1.7A3.107,3.107,0,0,1,256,10.911V8.061h1.005v2.85a2.128,2.128,0,0,0,.173.85,2.2,2.2,0,0,0,.463.693,2.281,2.281,0,0,0,.7.472,1.981,1.981,0,0,0,.848.173h4.68a2.113,2.113,0,0,0,.848-.173,2.195,2.195,0,0,0,.691-.464,2.288,2.288,0,0,0,.471-.7,2,2,0,0,0,.173-.85V8.061Z" transform="translate(-244.073 14)"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

View file

@ -0,0 +1,19 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="322" height="44" viewBox="0 0 322 44">
<defs>
<clipPath id="clip-On-On_Dark">
<rect width="322" height="44"/>
</clipPath>
</defs>
<g id="On-On_Dark" data-name="On-On Dark" clip-path="url(#clip-On-On_Dark)">
<rect width="322" height="44" fill="#f1f1f1"/>
<g id="Background" fill="#1f1f1f">
<path d="M 321.5 43.5 L 0.5 43.5 L 0.5 0.5 L 321.5 0.5 L 321.5 43.5 Z" stroke="none"/>
<path d="M 1 1 L 1 43 L 321 43 L 321 1 L 1 1 M 0 0 L 322 0 L 322 44 L 0 44 L 0 0 Z" stroke="none" fill="#313131"/>
</g>
<rect id="Divider" width="1" height="24" transform="translate(161 10)" fill="#313131"/>
<text id="Camera_label" data-name="Camera label" transform="translate(200 13)" fill="#fff" font-size="12" font-family="SegoeUI, Segoe UI"><tspan x="0" y="13">Camera on</tspan></text>
<path id="Camera_on_icon" data-name="Camera on icon" d="M16,512.359,12,510.3V512.3H0v-8.231H12v1.993L16,504Zm-5-7.266H1v6.173H11Zm4,.571-3,1.551v1.929l3,1.551Z" transform="translate(171 -486)" fill="#fff"/>
<text id="Mic_label" data-name="Mic label" transform="translate(38 13)" fill="#fff" font-size="12" font-family="SegoeUI, Segoe UI"><tspan x="0" y="13">Microphone on</tspan></text>
<path id="Mic_on_icon" data-name="Mic on icon" d="M259.518,12.092a1.476,1.476,0,0,1-.589-.118,1.5,1.5,0,0,1-.8-.8,1.486,1.486,0,0,1-.118-.59V1.511a1.486,1.486,0,0,1,.118-.59,1.5,1.5,0,0,1,.8-.8A1.476,1.476,0,0,1,259.518,0h4.02a1.476,1.476,0,0,1,.589.118,1.5,1.5,0,0,1,.8.8,1.487,1.487,0,0,1,.118.59V10.58a1.487,1.487,0,0,1-.118.59,1.5,1.5,0,0,1-.8.8,1.476,1.476,0,0,1-.589.118Zm-.5-1.511a.51.51,0,0,0,.5.5h4.02a.51.51,0,0,0,.5-.5V1.511a.485.485,0,0,0-.149-.354.482.482,0,0,0-.353-.15h-4.02a.482.482,0,0,0-.353.15.485.485,0,0,0-.149.354Zm8.04-2.519v2.85a3.106,3.106,0,0,1-.251,1.244,3.2,3.2,0,0,1-1.7,1.7,3.085,3.085,0,0,1-1.241.252H262.03v1.008h2.01v1.008h-5.025V15.115h2.01V14.107h-1.837a3.085,3.085,0,0,1-1.241-.252,3.2,3.2,0,0,1-1.7-1.7A3.107,3.107,0,0,1,256,10.911V8.061h1.005v2.85a2.128,2.128,0,0,0,.173.85,2.2,2.2,0,0,0,.463.693,2.281,2.281,0,0,0,.7.472,1.981,1.981,0,0,0,.848.173h4.68a2.113,2.113,0,0,0,.848-.173,2.195,2.195,0,0,0,.691-.464,2.288,2.288,0,0,0,.471-.7,2,2,0,0,0,.173-.85V8.061Z" transform="translate(-244.073 14)" fill="#fff"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

View file

@ -0,0 +1,19 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="322" height="44" viewBox="0 0 322 44">
<defs>
<clipPath id="clip-On-On_Light">
<rect width="322" height="44"/>
</clipPath>
</defs>
<g id="On-On_Light" data-name="On-On Light" clip-path="url(#clip-On-On_Light)">
<rect width="322" height="44" fill="#f1f1f1"/>
<g id="Background" fill="#f1f1f1">
<path d="M 321.5 43.5 L 0.5 43.5 L 0.5 0.5 L 321.5 0.5 L 321.5 43.5 Z" stroke="none"/>
<path d="M 1 1 L 1 43 L 321 43 L 321 1 L 1 1 M 0 0 L 322 0 L 322 44 L 0 44 L 0 0 Z" stroke="none" fill="#dbdbdb"/>
</g>
<rect id="Divider" width="1" height="24" transform="translate(161 10)" fill="#dbdbdb"/>
<text id="Camera_label" data-name="Camera label" transform="translate(200 13)" font-size="12" font-family="SegoeUI, Segoe UI"><tspan x="0" y="13">Camera on</tspan></text>
<path id="Camera_on_icon" data-name="Camera on icon" d="M16,512.359,12,510.3V512.3H0v-8.231H12v1.993L16,504Zm-5-7.266H1v6.173H11Zm4,.571-3,1.551v1.929l3,1.551Z" transform="translate(171 -486)"/>
<text id="Mic_label" data-name="Mic label" transform="translate(38 13)" font-size="12" font-family="SegoeUI, Segoe UI"><tspan x="0" y="13">Microphone on</tspan></text>
<path id="Mic_on_icon" data-name="Mic on icon" d="M259.518,12.092a1.476,1.476,0,0,1-.589-.118,1.5,1.5,0,0,1-.8-.8,1.486,1.486,0,0,1-.118-.59V1.511a1.486,1.486,0,0,1,.118-.59,1.5,1.5,0,0,1,.8-.8A1.476,1.476,0,0,1,259.518,0h4.02a1.476,1.476,0,0,1,.589.118,1.5,1.5,0,0,1,.8.8,1.487,1.487,0,0,1,.118.59V10.58a1.487,1.487,0,0,1-.118.59,1.5,1.5,0,0,1-.8.8,1.476,1.476,0,0,1-.589.118Zm-.5-1.511a.51.51,0,0,0,.5.5h4.02a.51.51,0,0,0,.5-.5V1.511a.485.485,0,0,0-.149-.354.482.482,0,0,0-.353-.15h-4.02a.482.482,0,0,0-.353.15.485.485,0,0,0-.149.354Zm8.04-2.519v2.85a3.106,3.106,0,0,1-.251,1.244,3.2,3.2,0,0,1-1.7,1.7,3.085,3.085,0,0,1-1.241.252H262.03v1.008h2.01v1.008h-5.025V15.115h2.01V14.107h-1.837a3.085,3.085,0,0,1-1.241-.252,3.2,3.2,0,0,1-1.7-1.7A3.107,3.107,0,0,1,256,10.911V8.061h1.005v2.85a2.128,2.128,0,0,0,.173.85,2.2,2.2,0,0,0,.463.693,2.281,2.281,0,0,0,.7.472,1.981,1.981,0,0,0,.848.173h4.68a2.113,2.113,0,0,0,.848-.173,2.195,2.195,0,0,0,.691-.464,2.288,2.288,0,0,0,.471-.7,2,2,0,0,0,.173-.85V8.061Z" transform="translate(-244.073 14)"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View file

@ -0,0 +1,311 @@
#include "pch.h"
#include "Overlay.h"
#include <windowsx.h>
#include "common/windows_colors.h"
#include "VideoConferenceModule.h"
OverlayImages Overlay::darkImages;
OverlayImages Overlay::lightImages;
bool Overlay::valueUpdated = false;
bool Overlay::cameraMuted = false;
bool Overlay::cameraInUse = false;
bool Overlay::microphoneMuted = false;
std::wstring Overlay::theme = L"system";
bool Overlay::hideOverlayWhenUnmuted = true;
std::vector<HWND> Overlay::hwnds;
UINT_PTR Overlay::nTimerId;
unsigned __int64 Overlay::lastTimeCamOrMicMuteStateChanged;
const int REFRESH_RATE = 100;
const int OVERLAY_SHOW_TIME = 500;
const int BORDER_OFFSET = 12;
Overlay::Overlay()
{
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");
}
LRESULT Overlay::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();
setCameraMute(VideoConferenceModule::getVirtualCameraMuteState());
}
return DefWindowProc(hwnd, msg, wparam, lparam);
}
case WM_CREATE:
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc;
hdc = BeginPaint(hwnd, &ps);
Gdiplus::Graphics graphic(hdc);
OverlayImages* themeImages = &darkImages;
if (theme == L"light" || (theme == L"system" && !WindowsColors::is_dark_mode()))
{
themeImages = &lightImages;
}
else
{
themeImages = &darkImages;
}
if (!cameraInUse)
{
if (microphoneMuted)
{
graphic.DrawImage(themeImages->camUnusedMicOff, 0, 0, themeImages->camUnusedMicOff->GetWidth(), themeImages->camUnusedMicOff->GetHeight());
}
else
{
graphic.DrawImage(themeImages->camUnusedMicOn, 0, 0, themeImages->camUnusedMicOn->GetWidth(), themeImages->camUnusedMicOn->GetHeight());
}
}
else if (microphoneMuted )
{
if (cameraMuted)
{
graphic.DrawImage(themeImages->camOffMicOff, 0, 0, themeImages->camOffMicOff->GetWidth(), themeImages->camOffMicOff->GetHeight());
}
else
{
graphic.DrawImage(themeImages->camOnMicOff, 0, 0, themeImages->camOnMicOff->GetWidth(), themeImages->camOnMicOff->GetHeight());
}
}
else
{
if (cameraMuted)
{
graphic.DrawImage(themeImages->camOffMicOn, 0, 0, themeImages->camOffMicOn->GetWidth(), themeImages->camOffMicOn->GetHeight());
}
else
{
graphic.DrawImage(themeImages->camOnMicOn, 0, 0, themeImages->camOnMicOn->GetWidth(), themeImages->camOnMicOn->GetHeight());
}
}
EndPaint(hwnd, &ps);
break;
}
case WM_TIMER:
{
cameraInUse = VideoConferenceModule::getVirtualCameraInUse();
InvalidateRect(hwnd, NULL, NULL);
using namespace std::chrono;
if (cameraInUse || microphoneMuted || !hideOverlayWhenUnmuted)
{
ShowWindow(hwnd, SW_SHOW);
}
else
{
if (duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count() - lastTimeCamOrMicMuteStateChanged > OVERLAY_SHOW_TIME || !valueUpdated)
{
ShowWindow(hwnd, SW_HIDE);
}
else
{
ShowWindow(hwnd, SW_SHOW);
}
}
KillTimer(hwnd, nTimerId);
break;
}
default:
return DefWindowProc(hwnd, msg, wparam, lparam);
}
nTimerId = SetTimer(hwnd, 101, REFRESH_RATE, NULL);
return DefWindowProc(hwnd, msg, wparam, lparam);
}
void Overlay::showOverlay(std::wstring position, std::wstring monitorString)
{
valueUpdated = false;
for (auto& hwnd : hwnds)
{
PostMessage(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 = GetModuleHandle(NULL);
wc.lpszClassName = CLASS_NAME;
wc.hCursor = LoadCursor(nullptr, IDC_ARROW);
wc.hbrBackground = (HBRUSH)COLOR_WINDOW;
wc.lpfnWndProc = WindowProcessMessages;
RegisterClass(&wc);
// Create the window
DWORD dwExtStyle = 0;
DWORD dwStyle = WS_POPUPWINDOW;
std::vector<MonitorInfo> 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 = CreateWindowEx(
WS_EX_TOOLWINDOW | WS_EX_LAYERED,
CLASS_NAME,
CLASS_NAME,
WS_POPUP,
positionX,
positionY,
overlayWidth,
overlayHeight,
NULL,
NULL,
GetModuleHandle(NULL),
NULL);
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 Overlay::hideOverlay()
{
for (auto& hwnd : hwnds)
{
PostMessage(hwnd, WM_CLOSE, 0, 0);
}
hwnds.clear();
}
bool Overlay::getCameraMute()
{
return cameraMuted;
}
void Overlay::setCameraMute(bool mute)
{
valueUpdated = true;
lastTimeCamOrMicMuteStateChanged = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
cameraMuted = mute;
}
bool Overlay::getMicrophoneMute()
{
return microphoneMuted;
}
void Overlay::setMicrophoneMute(bool mute)
{
if (mute != microphoneMuted)
{
valueUpdated = true;
lastTimeCamOrMicMuteStateChanged = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
}
microphoneMuted = mute;
}
void Overlay::setHideOverlayWhenUnmuted(bool hide)
{
hideOverlayWhenUnmuted = hide;
}
void Overlay::setTheme(std::wstring theme)
{
Overlay::theme = theme;
}

View file

@ -0,0 +1,55 @@
#pragma once
#include <Windows.h>
#include <gdiplus.h>
#include "common/monitors.h"
struct OverlayImages
{
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 Overlay
{
public:
Overlay();
static void showOverlay(std::wstring position, std::wstring monitorString);
static void hideOverlay();
bool static getCameraMute();
void static setCameraMute(bool mute);
bool static getMicrophoneMute();
void static setMicrophoneMute(bool mute);
void static setTheme(std::wstring theme);
void static setHideOverlayWhenUnmuted(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
static std::vector<HWND> hwnds;
static OverlayImages darkImages;
static OverlayImages lightImages;
static bool valueUpdated;
static bool cameraMuted;
static bool cameraInUse;
static bool microphoneMuted;
static std::wstring theme;
static bool hideOverlayWhenUnmuted;
static unsigned __int64 lastTimeCamOrMicMuteStateChanged;
static UINT_PTR nTimerId;
};

View file

@ -0,0 +1,26 @@
# Windows Key Shortcut Guide
# Introduction
The Windows Key Shortcut Guide shows common keyboard shortcuts that use the Windows key.
# Usage
Press and hold the keyboard Windows key for about 1 second, an overlay appears showing keyboard shortcuts that use the Windows Key:
- Shortcuts for changing the position of the active window.
- Common Windows shortcuts.
- Taskbar shortcuts.
Releasing the Windows key will make the overlay disappear. If the shortcut guide was visible for less than a second, the start menu will appear after the shortcut guide is dismissed.
![Image of the Overlay](/doc/images/shortcut_guide/usage.png)
Windows key keyboard shortcuts can be used while the guide is being shown and the result of those shortcuts (active window moved, arrow shortcut behavior changes, etc) will be displayed in the guide.
# Options
These configurations can be edited from the PowerToys Settings screen:
- "How long to press the Windows key before showing the Shortcut Guide (ms)" - How many milliseconds to press the Windows key before the Shortcut Guide is shown.
- "Opacity of the Shortcut Guide's overlay background (%)" - Changing this setting controls the opacity of the Shortcut Guide's overlay background, occluding the work environment beneath the Shortcut Guide to different degrees.
![Image of the Options](/doc/images/shortcut_guide/settings.png)
# Backlog
The backlog for the utility can be found [here](https://github.com/Microsoft/PowerToys/tree/master/doc/planning/ShortcutGuideBacklog.md) and the source code is [here](https://github.com/Microsoft/PowerToys/tree/master/src/modules/shortcut_guide).

View file

@ -0,0 +1,55 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<ClCompile Include="pch.cpp" />
<ClCompile Include="dllmain.cpp" />
<ClCompile Include="keyboard_state.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="shortcut_guide.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="target_state.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="overlay_window.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="trace.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="pch.h" />
<ClInclude Include="keyboard_state.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="shortcut_guide.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="target_state.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="overlay_window.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="trace.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="resource.h" />
</ItemGroup>
<ItemGroup>
<Filter Include="Header Files">
<UniqueIdentifier>{2c7c97f7-0d87-4230-a4b2-baf2cfc35d58}</UniqueIdentifier>
</Filter>
<Filter Include="Source Files">
<UniqueIdentifier>{aa4b6713-589d-42ef-804d-3a045833f83f}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="shortcut_guide.rc" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\..\..\hasWDK.props" />
<Import Project="WDK_Video Conference.vcxproj" Condition="$(HasWDK)==true" />
<Import Project="..\..\..\wdk_skip.vcxproj" Condition="$(HasWDK)==false" />
<PropertyGroup Label="Globals">
<IntDirSharingDetected>None</IntDirSharingDetected>
</PropertyGroup>
</Project>

View file

@ -0,0 +1,444 @@
#include "pch.h"
#include "VideoConferenceModule.h"
#include <WinUser.h>
#include <gdiplus.h>
#include <common/common.h>
#include <common/debug_control.h>
#include <CameraStateUpdateChannels.h>
#include "logging.h"
extern "C" IMAGE_DOS_HEADER __ImageBase;
VideoConferenceModule* instance = nullptr;
Overlay VideoConferenceModule::overlay;
CVolumeNotification* VideoConferenceModule::volumeNotification;
PowerToysSettings::HotkeyObject VideoConferenceModule::cameraAndMicrophoneMuteHotkey = PowerToysSettings::HotkeyObject::from_settings(true, false, false, false, 78);
PowerToysSettings::HotkeyObject VideoConferenceModule::microphoneMuteHotkey = PowerToysSettings::HotkeyObject::from_settings(true, false, false, true, 65);
PowerToysSettings::HotkeyObject VideoConferenceModule::cameraMuteHotkey = PowerToysSettings::HotkeyObject::from_settings(true, false, false, true, 79);
std::wstring VideoConferenceModule::overlayPositionString;
std::wstring VideoConferenceModule::overlayMonitorString;
std::wstring VideoConferenceModule::selectedCamera;
std::wstring VideoConferenceModule::imageOverlayPath;
HHOOK VideoConferenceModule::hook_handle;
IAudioEndpointVolume* endpointVolume = NULL;
bool VideoConferenceModule::isKeyPressed(unsigned int keyCode)
{
return (GetKeyState(keyCode) & 0x8000);
}
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()
{
IMMDeviceEnumerator* deviceEnumerator = NULL;
if (CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER, __uuidof(IMMDeviceEnumerator), (LPVOID*)&deviceEnumerator) == S_OK)
{
IMMDevice* defaultDevice = NULL;
if (deviceEnumerator->GetDefaultAudioEndpoint(eCapture, eCommunications, &defaultDevice) == S_OK)
{
deviceEnumerator->Release();
deviceEnumerator = NULL;
IAudioEndpointVolume* microphoneEndpoint = NULL;
if (defaultDevice->Activate(__uuidof(IAudioEndpointVolume), CLSCTX_INPROC_SERVER, NULL, (LPVOID*)&microphoneEndpoint) == S_OK)
{
volumeNotification = new CVolumeNotification();
microphoneEndpoint->RegisterControlChangeNotify(volumeNotification);
BOOL currentMute;
if (microphoneEndpoint->GetMute(&currentMute) == S_OK)
{
if (microphoneEndpoint->SetMute(!currentMute, NULL) == S_OK)
{
//overlay.setMicrophoneMute(!currentMute);
}
}
defaultDevice->Release();
defaultDevice = NULL;
microphoneEndpoint->Release();
}
}
}
}
bool VideoConferenceModule::getMicrophoneMuteState()
{
HRESULT hr;
BOOL currentMute = false;
IMMDeviceEnumerator* deviceEnumerator = NULL;
if (CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER, __uuidof(IMMDeviceEnumerator), (LPVOID*)&deviceEnumerator) == S_OK)
{
IMMDevice* defaultDevice = NULL;
if (deviceEnumerator->GetDefaultAudioEndpoint(eCapture, eCommunications, &defaultDevice) == S_OK)
{
deviceEnumerator->Release();
deviceEnumerator = NULL;
IAudioEndpointVolume* endpointVolume = NULL;
if (defaultDevice->Activate(__uuidof(IAudioEndpointVolume), CLSCTX_INPROC_SERVER, NULL, (LPVOID*)&endpointVolume) == S_OK)
{
volumeNotification = new CVolumeNotification();
hr = endpointVolume->RegisterControlChangeNotify(volumeNotification);
defaultDevice->Release();
defaultDevice = NULL;
endpointVolume->GetMute(&currentMute);
volumeNotification->Release();
}
}
}
return currentMute;
}
void VideoConferenceModule::reverseVirtualCameraMuteState()
{
if (!instance->_settingsUpdateChannel.has_value())
{
return;
}
instance->_settingsUpdateChannel->access([](auto settingsMemory) {
auto settings = reinterpret_cast<CameraSettingsUpdateChannel*>(settingsMemory._data);
settings->useOverlayImage = !settings->useOverlayImage;
});
}
bool VideoConferenceModule::getVirtualCameraMuteState()
{
bool disabled = false;
if (!instance->_settingsUpdateChannel.has_value())
{
return disabled;
}
instance->_settingsUpdateChannel->access([&disabled](auto settingsMemory) {
auto settings = reinterpret_cast<CameraSettingsUpdateChannel*>(settingsMemory._data);
disabled = settings->useOverlayImage;
});
return disabled;
}
bool VideoConferenceModule::getVirtualCameraInUse()
{
bool inUse = false;
if (!instance->_settingsUpdateChannel.has_value())
{
return inUse;
}
instance->_settingsUpdateChannel->access([&inUse](auto settingsMemory) {
auto settings = reinterpret_cast<CameraSettingsUpdateChannel*>(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<KBDLLHOOKSTRUCT*>(lParam);
if (isHotkeyPressed(kbd->vkCode, cameraAndMicrophoneMuteHotkey))
{
reverseMicrophoneMute();
if (overlay.getCameraMute() != overlay.getMicrophoneMute())
{
reverseVirtualCameraMuteState();
overlay.setCameraMute(getVirtualCameraMuteState());
}
}
else if (isHotkeyPressed(kbd->vkCode, microphoneMuteHotkey))
{
reverseMicrophoneMute();
}
else if (isHotkeyPressed(kbd->vkCode, cameraMuteHotkey))
{
reverseVirtualCameraMuteState();
overlay.setCameraMute(getVirtualCameraMuteState());
}
}
}
return CallNextHookEx(hook_handle, nCode, wParam, lParam);
}
VideoConferenceModule::VideoConferenceModule()
{
init_settings();
_settingsUpdateChannel =
SerializedSharedMemory::create(CameraSettingsUpdateChannel::endpoint(), sizeof(CameraSettingsUpdateChannel), false);
if (_settingsUpdateChannel)
{
_settingsUpdateChannel->access([](auto memory) {
auto updatesChannel = new (memory._data) CameraSettingsUpdateChannel{};
});
}
sendSourceCameraNameUpdate();
sendOverlayImageUpdate();
overlay.showOverlay(overlayPositionString, overlayMonitorString);
}
inline VideoConferenceModule::~VideoConferenceModule()
{
if (overlay.getCameraMute())
{
reverseVirtualCameraMuteState();
overlay.setCameraMute(getVirtualCameraMuteState());
}
if (overlay.getMicrophoneMute())
{
reverseMicrophoneMute();
}
overlay.hideOverlay();
}
const wchar_t* VideoConferenceModule::get_name()
{
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);
values.save_to_settings_file();
//Trace::SettingsChanged(pressTime.value, overlayOpacity.value, theme.value);
if (_enabled)
{
if (const auto val = values.get_json(L"mute_camera_and_microphone_hotkey"))
{
cameraAndMicrophoneMuteHotkey = PowerToysSettings::HotkeyObject::from_json(*val);
}
if (const auto val = values.get_json(L"mute_microphone_hotkey"))
{
microphoneMuteHotkey = PowerToysSettings::HotkeyObject::from_json(*val);
}
if (const auto val = values.get_json(L"mute_camera_hotkey"))
{
cameraMuteHotkey = PowerToysSettings::HotkeyObject::from_json(*val);
}
if (const auto val = values.get_string_value(L"overlay_position"))
{
overlayPositionString = val.value();
}
if (const auto val = values.get_string_value(L"overlay_monitor"))
{
overlayMonitorString = val.value();
}
if (const auto val = values.get_string_value(L"selected_camera"); val && val != selectedCamera)
{
selectedCamera = val.value();
sendSourceCameraNameUpdate();
}
if (const auto val = values.get_string_value(L"camera_overlay_image_path"); val && val != imageOverlayPath)
{
imageOverlayPath = val.value();
sendOverlayImageUpdate();
}
if (const auto val = values.get_string_value(L"theme"))
{
Overlay::setTheme(val.value());
}
if (const auto val = values.get_bool_value(L"hide_overlay_when_unmuted"))
{
Overlay::setHideOverlayWhenUnmuted(val.value());
}
overlay.showOverlay(overlayPositionString, overlayMonitorString);
}
}
catch (...)
{
// Improper JSON. TODO: handle the error.
}
}
void VideoConferenceModule::init_settings()
{
try
{
PowerToysSettings::PowerToyValues settings = PowerToysSettings::PowerToyValues::load_from_settings_file(L"Video Conference");
if (const auto val = settings.get_json(L"mute_camera_and_microphone_hotkey"))
{
cameraAndMicrophoneMuteHotkey = PowerToysSettings::HotkeyObject::from_json(*val);
}
if (const auto val = settings.get_json(L"mute_microphone_hotkey"))
{
microphoneMuteHotkey = PowerToysSettings::HotkeyObject::from_json(*val);
}
if (const auto val = settings.get_json(L"mute_camera_hotkey"))
{
cameraMuteHotkey = PowerToysSettings::HotkeyObject::from_json(*val);
}
if (const auto val = settings.get_string_value(L"overlay_position"))
{
overlayPositionString = val.value();
}
if (const auto val = settings.get_string_value(L"overlay_monitor"))
{
overlayMonitorString = val.value();
}
if (const auto val = settings.get_string_value(L"selected_camera"))
{
selectedCamera = val.value();
}
if (const auto val = settings.get_string_value(L"camera_overlay_image_path"))
{
imageOverlayPath = val.value();
}
if (const auto val = settings.get_string_value(L"theme"))
{
Overlay::setTheme(val.value());
}
if (const auto val = settings.get_bool_value(L"hide_overlay_when_unmuted"))
{
Overlay::setHideOverlayWhenUnmuted(val.value());
}
}
catch (std::exception&)
{
// Error while loading from the settings file. Just let default values stay as they are.
}
}
void VideoConferenceModule::enable()
{
if (!_enabled)
{
overlay.setMicrophoneMute(getMicrophoneMuteState());
overlay.setCameraMute(getVirtualCameraMuteState());
overlay.showOverlay(overlayPositionString, overlayMonitorString);
_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::disable()
{
if (_enabled)
{
if (hook_handle)
{
bool success = UnhookWindowsHookEx(hook_handle);
if (success)
{
hook_handle = nullptr;
}
}
if (overlay.getCameraMute())
{
reverseVirtualCameraMuteState();
overlay.setCameraMute(getVirtualCameraMuteState());
}
if (overlay.getMicrophoneMute())
{
reverseMicrophoneMute();
}
overlay.hideOverlay();
_enabled = false;
}
}
bool VideoConferenceModule::is_enabled()
{
return _enabled;
}
void VideoConferenceModule::destroy()
{
delete this;
instance = nullptr;
}
void VideoConferenceModule::sendSourceCameraNameUpdate()
{
if (!_settingsUpdateChannel.has_value() || selectedCamera.empty())
{
return;
}
_settingsUpdateChannel->access([](auto memory) {
auto updatesChannel = reinterpret_cast<CameraSettingsUpdateChannel*>(memory._data);
updatesChannel->sourceCameraName.emplace();
std::copy(begin(selectedCamera), end(selectedCamera), begin(*updatesChannel->sourceCameraName));
});
}
void VideoConferenceModule::sendOverlayImageUpdate()
{
if (!_settingsUpdateChannel.has_value())
{
return;
}
_imageOverlayChannel.reset();
TCHAR* powertoysDirectory = new TCHAR[255];
DWORD length = GetModuleFileName(NULL, powertoysDirectory, 255);
PathRemoveFileSpec(powertoysDirectory);
std::wstring blankImagePath(powertoysDirectory);
blankImagePath += L"\\modules\\VideoConference\\black.bmp";
_imageOverlayChannel = SerializedSharedMemory::create_readonly(CameraOverlayImageChannel::endpoint(),
imageOverlayPath != L"" ? imageOverlayPath : blankImagePath);
const size_t imageSize = _imageOverlayChannel->size();
_settingsUpdateChannel->access([imageSize](auto memory) {
auto updatesChannel = reinterpret_cast<CameraSettingsUpdateChannel*>(memory._data);
updatesChannel->overlayImageSize.emplace(imageSize);
updatesChannel->newOverlayImagePosted = true;
});
}

View file

@ -0,0 +1,69 @@
#pragma once
#include <mmdeviceapi.h>
#include <endpointvolume.h>
#include <interface/powertoy_module_interface.h>
#include "common/settings_objects.h"
#include "Overlay.h"
#include "CVolumeNotification.h"
#include <SerializedSharedMemory.h>
extern class VideoConferenceModule* instance;
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;
void sendSourceCameraNameUpdate();
void sendOverlayImageUpdate();
static void reverseMicrophoneMute();
static bool getMicrophoneMuteState();
static void reverseVirtualCameraMuteState();
static bool getVirtualCameraMuteState();
static bool getVirtualCameraInUse();
private:
void init_settings();
// 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::optional<SerializedSharedMemory> _imageOverlayChannel;
std::optional<SerializedSharedMemory> _settingsUpdateChannel;
static Overlay overlay;
static CVolumeNotification* volumeNotification;
static PowerToysSettings::HotkeyObject cameraAndMicrophoneMuteHotkey;
static PowerToysSettings::HotkeyObject microphoneMuteHotkey;
static PowerToysSettings::HotkeyObject cameraMuteHotkey;
static std::wstring overlayPositionString;
static std::wstring overlayMonitorString;
static std::wstring selectedCamera;
static std::wstring imageOverlayPath;
};

View file

@ -0,0 +1,174 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200729.8\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200729.8\build\native\Microsoft.Windows.CppWinRT.props')" />
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<VCProjectVersion>15.0</VCProjectVersion>
<ProjectGuid>{FD2CAFFC-D682-4ED9-A06B-5FC88AE0A193}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<RootNamespace>overlaywindow</RootNamespace>
<WindowsTargetPlatformVersion>10.0.17134.0</WindowsTargetPlatformVersion>
<ProjectName>VideoConferenceModule</ProjectName>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
<SpectreMitigation>Spectre</SpectreMitigation>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
<SpectreMitigation>Spectre</SpectreMitigation>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
<Import Project="..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.200519.2\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.200519.2\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200729.8\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200729.8\build\native\Microsoft.Windows.CppWinRT.targets')" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<LinkIncremental>false</LinkIncremental>
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\modules\VideoConference\</OutDir>
<IntDir>$(SolutionDir)$(Platform)\$(Configuration)\obj\$(ProjectName)\</IntDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<LinkIncremental>true</LinkIncremental>
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\modules\VideoConference\</OutDir>
<IntDir>$(SolutionDir)$(Platform)\$(Configuration)\obj\$(ProjectName)\</IntDir>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<PrecompiledHeader>Use</PrecompiledHeader>
<WarningLevel>Level3</WarningLevel>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>NDEBUG;OVERLAYWINDOW_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<AdditionalIncludeDirectories>..\..\..\common\inc;..\..\..\common\Telemetry;..\..\..\;..\..\;..\VideoConferenceShared\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<LanguageStandard>stdcpplatest</LanguageStandard>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
<AdditionalDependencies>mfplat.lib;mf.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;gdiplus.lib;dwmapi.lib;uxtheme.lib;shcore.lib;Wtsapi32.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
<PostBuildEvent>
<Command>xcopy /y /I "$(ProjectDir)Icons\*" "$(OutDir)Icons"
xcopy /y /I "$(ProjectDir)black.bmp*" "$(OutDir)"</Command>
</PostBuildEvent>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<PrecompiledHeader>Use</PrecompiledHeader>
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_DEBUG;OVERLAYWINDOW_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
<AdditionalIncludeDirectories>..\..\..\common\inc;..\..\..\common\Telemetry;..\..\..\;..\..\;..\VideoConferenceShared\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<LanguageStandard>stdcpplatest</LanguageStandard>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
<AdditionalDependencies>mfplat.lib;mf.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;gdiplus.lib;dwmapi.lib;uxtheme.lib;shcore.lib;Wtsapi32.lib;dxguid.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
<PostBuildEvent>
<Command>xcopy /y /I "$(ProjectDir)Icons\*" "$(OutDir)Icons"
xcopy /y /I "$(ProjectDir)black.bmp*" "$(OutDir)"</Command>
</PostBuildEvent>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="CVolumeNotification.h" />
<ClInclude Include="framework.h" />
<ClInclude Include="Overlay.h" />
<ClInclude Include="pch.h" />
<ClInclude Include="VideoConferenceModule.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="CVolumeNotification.cpp" />
<ClCompile Include="dllmain.cpp" />
<ClCompile Include="Overlay.cpp" />
<ClCompile Include="pch.cpp">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader>
</ClCompile>
<ClCompile Include="VideoConferenceModule.cpp" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\common\common.vcxproj">
<Project>{74485049-c722-400f-abe5-86ac52d929b3}</Project>
</ProjectReference>
<ProjectReference Include="..\VideoConferenceShared\VideoConferenceShared.vcxproj">
<Project>{459e0768-7ebd-4c41-bba1-6db3b3815e0a}</Project>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="Icons\Off-NotInUse Dark.svg" />
<None Include="Icons\Off-NotInUse Light.svg" />
<None Include="Icons\Off-Off Dark.svg" />
<None Include="Icons\Off-Off Light.svg" />
<None Include="Icons\Off-On Dark.svg" />
<None Include="Icons\Off-On Light.svg" />
<None Include="Icons\On-NotInUse Dark.svg" />
<None Include="Icons\On-NotInUse Light.svg" />
<None Include="Icons\On-Off Dark.svg" />
<None Include="Icons\On-Off Light.svg" />
<None Include="Icons\On-On Dark.svg" />
<None Include="Icons\On-On Light.svg" />
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<Image Include="black.bmp" />
<Image Include="Icons\Off-NotInUse Dark.png" />
<Image Include="Icons\Off-NotInUse Light.png" />
<Image Include="Icons\Off-Off Dark.png" />
<Image Include="Icons\Off-Off Light.png" />
<Image Include="Icons\Off-On Dark.png" />
<Image Include="Icons\Off-On Light.png" />
<Image Include="Icons\On-NotInUse Dark.png" />
<Image Include="Icons\On-NotInUse Light.png" />
<Image Include="Icons\On-Off Dark.png" />
<Image Include="Icons\On-Off Light.png" />
<Image Include="Icons\On-On Dark.png" />
<Image Include="Icons\On-On Light.png" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>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}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.200519.2\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.200519.2\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200729.8\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200729.8\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200729.8\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200729.8\build\native\Microsoft.Windows.CppWinRT.targets'))" />
</Target>
</Project>

View file

@ -0,0 +1,124 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<ClCompile Include="pch.cpp" />
<ClCompile Include="dllmain.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Overlay.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="VideoConferenceModule.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="CVolumeNotification.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="pch.h" />
<ClInclude Include="framework.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="Overlay.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="VideoConferenceModule.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="CVolumeNotification.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{54fb4e67-1e48-4706-baa2-a0d17f4f683f}</UniqueIdentifier>
</Filter>
<Filter Include="Header Files">
<UniqueIdentifier>{fc4156a6-5a50-4c1b-a6a6-bc6c38dde579}</UniqueIdentifier>
</Filter>
<Filter Include="Icons">
<UniqueIdentifier>{370ee2ac-72fd-4f10-806f-4e8b680fb5c1}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
<None Include="Icons\On-Off Dark.svg">
<Filter>Icons</Filter>
</None>
<None Include="Icons\On-Off Light.svg">
<Filter>Icons</Filter>
</None>
<None Include="Icons\On-On Dark.svg">
<Filter>Icons</Filter>
</None>
<None Include="Icons\On-On Light.svg">
<Filter>Icons</Filter>
</None>
<None Include="Icons\Off-NotInUse Dark.svg">
<Filter>Icons</Filter>
</None>
<None Include="Icons\Off-NotInUse Light.svg">
<Filter>Icons</Filter>
</None>
<None Include="Icons\Off-Off Dark.svg">
<Filter>Icons</Filter>
</None>
<None Include="Icons\Off-Off Light.svg">
<Filter>Icons</Filter>
</None>
<None Include="Icons\Off-On Dark.svg">
<Filter>Icons</Filter>
</None>
<None Include="Icons\Off-On Light.svg">
<Filter>Icons</Filter>
</None>
<None Include="Icons\On-NotInUse Dark.svg">
<Filter>Icons</Filter>
</None>
<None Include="Icons\On-NotInUse Light.svg">
<Filter>Icons</Filter>
</None>
</ItemGroup>
<ItemGroup>
<Image Include="Icons\On-Off Light.png">
<Filter>Icons</Filter>
</Image>
<Image Include="Icons\On-On Dark.png">
<Filter>Icons</Filter>
</Image>
<Image Include="Icons\On-On Light.png">
<Filter>Icons</Filter>
</Image>
<Image Include="Icons\Off-NotInUse Dark.png">
<Filter>Icons</Filter>
</Image>
<Image Include="Icons\Off-NotInUse Light.png">
<Filter>Icons</Filter>
</Image>
<Image Include="Icons\Off-Off Dark.png">
<Filter>Icons</Filter>
</Image>
<Image Include="Icons\Off-Off Light.png">
<Filter>Icons</Filter>
</Image>
<Image Include="Icons\Off-On Dark.png">
<Filter>Icons</Filter>
</Image>
<Image Include="Icons\Off-On Light.png">
<Filter>Icons</Filter>
</Image>
<Image Include="Icons\On-NotInUse Dark.png">
<Filter>Icons</Filter>
</Image>
<Image Include="Icons\On-NotInUse Light.png">
<Filter>Icons</Filter>
</Image>
<Image Include="Icons\On-Off Dark.png">
<Filter>Icons</Filter>
</Image>
<Image Include="black.bmp">
<Filter>Icons</Filter>
</Image>
</ItemGroup>
</Project>

Binary file not shown.

After

Width:  |  Height:  |  Size: 822 B

View file

@ -0,0 +1,32 @@
// dllmain.cpp : Defines the entry point for the DLL application.
#include "pch.h"
#include <interface/powertoy_module_interface.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:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
extern "C" __declspec(dllexport) PowertoyModuleIface* __cdecl powertoy_create()
{
if (!instance)
{
instance = new VideoConferenceModule();
return instance;
}
else
{
return nullptr;
}
}

View file

@ -0,0 +1,5 @@
#pragma once
#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
// Windows Header Files
#include <windows.h>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Windows.CppWinRT" version="2.0.200729.8" targetFramework="native" />
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.200519.2" targetFramework="native" />
</packages>

View file

@ -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.

View file

@ -0,0 +1,26 @@
#pragma once
#include <winrt/base.h>
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Foundation.Collections.h>
#include <Windows.h>
#include <dxgi1_3.h>
#include <d3d11_2.h>
#include <d2d1_3.h>
#include <d2d1_3helper.h>
#include <d2d1helper.h>
#include <dwrite.h>
#include <dcomp.h>
#include <dwmapi.h>
#include <Shobjidl.h>
#include <Shlwapi.h>
#include <string>
#include <algorithm>
#include <chrono>
#include <mutex>
#include <thread>
#include <functional>
#include <condition_variable>
#include <stdexcept>
#include <tuple>
#include <unordered_set>
#include <string>

View file

@ -0,0 +1,15 @@
#include "CameraStateUpdateChannels.h"
#include <common/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;
}

View file

@ -0,0 +1,23 @@
#pragma once
#include <optional>
#include <string_view>
#include <array>
struct CameraSettingsUpdateChannel
{
bool useOverlayImage = false;
bool cameraInUse = false;
std::optional<size_t> overlayImageSize;
std::optional<std::array<wchar_t, 256>> sourceCameraName;
bool newOverlayImagePosted = false;
static std::wstring_view endpoint();
};
namespace CameraOverlayImageChannel
{
std::wstring_view endpoint();
}

View file

@ -0,0 +1,176 @@
#include "Logging.h"
#include <iostream>
#include <fstream>
#include <mutex>
#include <iomanip>
#include <chrono>
#include <filesystem>
#include <mfapi.h>
std::mutex logMutex;
void LogToFile(std::string what, const bool verbose)
{
const auto tempPath = std::filesystem::temp_directory_path();
if (verbose)
{
const bool verboseIndicatorFilePresent = std::filesystem::exists(tempPath / L"PowerToysVideoConferenceVerbose.flag");
if (!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::ofstream myfile;
std::wstring logFilePath = tempPath;
logFilePath += L"\\PowerToysVideoConference.log";
myfile.open(logFilePath, std::fstream::app);
static const auto newLaunch = [&] {
myfile << prefix << "<<<NEW SESSION>>\n\n";
return 0;
}();
myfile << prefix << what << "\n";
myfile.close();
}
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";
}

View file

@ -0,0 +1,31 @@
#pragma once
#include <string>
#include <guiddef.h>
#include <system_error>
void LogToFile(std::string 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); \
});

View file

@ -0,0 +1,187 @@
#include "SerializedSharedMemory.h"
inline char* SerializedSharedMemory::lock_flag_addr() noexcept
{
return reinterpret_cast<char*>(_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<wil::unique_handle, 2> 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 = {};
rhs._read_only = true;
return *this;
}
std::optional<SerializedSharedMemory> 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<uint8_t*>(
MapViewOfFile(hMapFile.get(), read_only ? FILE_MAP_READ : FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, UISize.QuadPart));
if (!shmem)
{
return std::nullopt;
}
std::array<wil::unique_handle, 2> handles = { std::move(hMapFile), {} };
return SerializedSharedMemory{ std::move(handles), memory_t{ shmem, size }, read_only };
}
std::optional<SerializedSharedMemory> 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<uint8_t*>(
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<wil::unique_handle, 2> handles = { std::move(hMapFile), {} };
return SerializedSharedMemory{ std::move(handles), memory_t{ shmem, size }, read_only };
}
std::optional<SerializedSharedMemory> 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<uint8_t*>(MapViewOfFile(nullptr, FILE_MAP_READ, 0, 0, fileSize.QuadPart));
if (shmem)
{
return std::nullopt;
}
std::array<wil::unique_handle, 2> handles = { std::move(hMapFile), std::move(hFile) };
return SerializedSharedMemory{ std::move(handles), memory_t{ shmem, static_cast<size_t>(fileSize.QuadPart) }, true };
}
void SerializedSharedMemory::access(std::function<void(memory_t)> access_routine) noexcept
{
lock();
access_routine(_memory);
unlock();
}

View file

@ -0,0 +1,54 @@
#pragma once
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <Windows.h>
#include <string>
#include <optional>
#include <wil/resource.h>
#include <functional>
#include <array>
// 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<SerializedSharedMemory> create(const std::wstring_view object_name,
const size_t size,
const bool read_only,
SECURITY_ATTRIBUTES* maybe_attributes = nullptr) noexcept;
static std::optional<SerializedSharedMemory> create_readonly(
const std::wstring_view object_name,
const std::wstring_view file_path,
SECURITY_ATTRIBUTES* maybe_attributes = nullptr) noexcept;
static std::optional<SerializedSharedMemory> open(const std::wstring_view object_name,
const size_t size,
const bool read_only) noexcept;
void access(std::function<void(memory_t)> access_routine) noexcept;
inline size_t size() const noexcept { return _memory._size; }
~SerializedSharedMemory() noexcept;
SerializedSharedMemory(SerializedSharedMemory&&) noexcept;
SerializedSharedMemory& operator=(SerializedSharedMemory&&) noexcept;
private:
std::array<wil::unique_handle, 2> _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<wil::unique_handle, 2> handles, memory_t memory, const bool readonly) noexcept;
};

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\..\..\hasWDK.props" />
<Import Project="WDK_VideoConferenceShared.vcxproj" Condition="$(HasWDK)==true" />
<Import Project="..\..\..\wdk_skip.vcxproj" Condition="$(HasWDK)==false" />
<PropertyGroup Label="Globals">
<IntDirSharingDetected>None</IntDirSharingDetected>
</PropertyGroup>
</Project>

View file

@ -0,0 +1,124 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<VCProjectVersion>16.0</VCProjectVersion>
<Keyword>Win32Proj</Keyword>
<ProjectGuid>{459e0768-7ebd-4c41-bba1-6db3b3815e0a}</ProjectGuid>
<RootNamespace>VideoConferenceShared</RootNamespace>
<WindowsTargetPlatformVersion>10.0.17134.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<LinkIncremental>true</LinkIncremental>
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\modules\VideoConference\</OutDir>
<IntDir>$(SolutionDir)$(Platform)\$(Configuration)\obj\$(ProjectName)\</IntDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<LinkIncremental>false</LinkIncremental>
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\modules\VideoConference\</OutDir>
<IntDir>$(SolutionDir)$(Platform)\$(Configuration)\obj\$(ProjectName)\</IntDir>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<WarningLevel>Level4</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
<TreatWarningAsError>true</TreatWarningAsError>
<AdditionalIncludeDirectories>$(SolutionDir)\src\;</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
<Lib>
<TreatLibWarningAsErrors>true</TreatLibWarningAsErrors>
</Lib>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<WarningLevel>Level4</WarningLevel>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<TreatWarningAsError>true</TreatWarningAsError>
<AdditionalIncludeDirectories>$(SolutionDir)\src\;</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
<Lib>
<TreatLibWarningAsErrors>true</TreatLibWarningAsErrors>
</Lib>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="CameraStateUpdateChannels.cpp" />
<ClCompile Include="Logging.cpp" />
<ClCompile Include="SerializedSharedMemory.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="CameraStateUpdateChannels.h" />
<ClInclude Include="Logging.h" />
<ClInclude Include="SerializedSharedMemory.h" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\common\common.vcxproj">
<Project>{74485049-c722-400f-abe5-86ac52d929b3}</Project>
</ProjectReference>
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
<Import Project="..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.200519.2\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.200519.2\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
</ImportGroup>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>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}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.200519.2\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.200519.2\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
</Target>
</Project>

View file

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;c++;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="Header Files">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
</Filter>
<Filter Include="Resource Files">
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="SerializedSharedMemory.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="CameraStateUpdateChannels.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Logging.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="SerializedSharedMemory.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="CameraStateUpdateChannels.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="Logging.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
</Project>

Some files were not shown because too many files have changed in this diff Show more