Compare commits

...

15 Commits

Author SHA1 Message Date
Seraphima Zykova
c9cb4a7435 [FancyZones] Convert ARGB color values to RGB (#6277) 2020-09-02 19:28:14 +03:00
yuyoyuppe
0292ece3a5 VC: disable for <18362 builds 2020-09-02 19:27:13 +03:00
Enrico Giordani
e2288c5593 Add aka.ms link for VideoConference wiki 2020-09-02 17:43:15 +02:00
Seraphima Zykova
b69b12e219 [FancyZones] HEX to RGB util (#6275) 2020-09-02 18:37:46 +03:00
Enrico Giordani
653cdd19ae Update version to 0.22 2020-09-01 18:47:33 +02:00
Enrico Giordani
7b7c2c864a Telemtry for VideoConference settings 2020-09-01 18:45:30 +02:00
yuyoyuppe
d762db4a10 VC: wrap module settings into a Settings struct 2020-09-01 19:10:15 +03:00
Enrico Giordani
e41c1a4b21 Telemetry for mic and camera muted 2020-09-01 17:42:56 +02:00
Enrico Giordani
efa756ac5c Add telemetry 2020-09-01 16:36:56 +02:00
Enrico Giordani
99578b239e Add icons folder to project 2020-09-01 16:20:13 +02:00
Enrico Giordani
8719aa480c Update devdec Readme 2020-09-01 15:52:44 +02:00
yuyoyuppe
676013d244 updating: support github pre-releases WARN: switch off in master 2020-09-01 15:22:42 +03:00
yuyoyuppe
c7b3d6d7ea VC: truncate log file after 10MBs 2020-09-01 13:38:18 +03:00
yuyoyuppe
97dd5ff449 VC: clarify "overlay" word usage 2020-09-01 12:50:11 +03:00
yuyoyuppe
795682242e add new VideoConference module for muting mic/cam
Co-authored-by: PrzemyslawTusinski <61138537+PrzemyslawTusinski@users.noreply.github.com>
2020-09-01 12:05:23 +03:00
130 changed files with 6919 additions and 97 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

@@ -74,6 +74,10 @@ modify --installpath "%ProgramFiles(x86)%\Microsoft Visual Studio\2019\%targetFo
--add Microsoft.VisualStudio.Component.VC.ATL.Spectre
```
**Optional step:**<br/>
4. to build the Video Conference module, install the [WDK version 1903](https://docs.microsoft.com/en-us/windows-hardware/drivers/other-wdk-downloads) ([direct download link](https://go.microsoft.com/fwlink/?linkid=2085767))<br />
During the installation, make sure that, when prompted, the `Install Windows Driver Kit Visual Studio extension` option is checked.
### Compiling Source Code
- Open `powertoys.sln` in Visual Studio, in the `Solutions Configuration` drop-down menu select `Release` or `Debug`, from the `Build` menu choose `Build Solution`.

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,26 @@
<Custom Action="RegisterPowerToysSchTask" After="InstallFiles">
NOT Installed and CREATESCHEDULEDTASK = 1
</Custom>
<?if $(var.HasWDK)="true" ?>
<Custom Action="SetCertifyVirtualCameraDriverParam" After="RegisterPowerToysSchTask" >
WINDOWSBUILDNUMBER >= 18362
</Custom>
<Custom Action="CertifyVirtualCameraDriver" After="SetCertifyVirtualCameraDriverParam" >
NOT Installed and WINDOWSBUILDNUMBER >= 18362
</Custom>
<Custom Action="SetInstallVirtualCameraDriverParam" After="CertifyVirtualCameraDriver" >
WINDOWSBUILDNUMBER >= 18362
</Custom>
<Custom Action="InstallVirtualCameraDriver" After="SetInstallVirtualCameraDriverParam" >
NOT Installed and WINDOWSBUILDNUMBER >= 18362
</Custom>
<Custom Action="SetUninstallVirtualCameraDriverParam" Before="UninstallVirtualCameraDriver" >
WINDOWSBUILDNUMBER >= 18362
</Custom>
<Custom Action="UninstallVirtualCameraDriver" Before="RemoveFiles">
Installed and WINDOWSBUILDNUMBER >= 18362
</Custom>
<?endif?>
<Custom Action="WixCloseApplications" Before="RemoveFiles" />
<Custom Action="RemovePowerToysSchTasks" After="RemoveFiles" />
<Custom Action="TelemetryLogInstallSuccess" After="InstallFinalize">
@@ -204,6 +225,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 +299,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 +676,38 @@
<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">
<Condition>WINDOWSBUILDNUMBER >= 18362</Condition>
<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">
<Condition>WINDOWSBUILDNUMBER >= 18362</Condition>
<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 +889,8 @@
<ComponentRef Id="vcredist_dlls" />
<ComponentRef Id="PowerToysSvgs" />
<ComponentRef Id="Module_ShortcutGuide" />
<ComponentRef Id="Module_VideoConference" />
<ComponentRef Id="Module_VideoConferenceIcons" />
<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

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Version>0.21.0</Version>
<Version>0.22.0</Version>
</PropertyGroup>
</Project>

View File

@@ -9,7 +9,7 @@
VersionHelper::VersionHelper(std::string str)
{
// Remove whitespaces chars and a leading 'v'
str = left_trim(trim(str), "v");
str = left_trim<char>(trim<char>(str), "v");
// Replace '.' with spaces
replace_chars(str, ".", ' ');

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

@@ -4,21 +4,28 @@
#include <string>
#include <algorithm>
constexpr inline std::string_view default_trim_arg = " \t\r\n";
inline std::string_view left_trim(std::string_view s, const std::string_view chars_to_trim = default_trim_arg)
template<typename CharT>
inline constexpr std::basic_string_view<CharT> default_trim_arg()
{
s.remove_prefix(std::min(s.find_first_not_of(chars_to_trim), size(s)));
return reinterpret_cast<CharT*>(" \t\r\n");
}
template <typename CharT>
inline std::basic_string_view<CharT> left_trim(std::basic_string_view<CharT> s, const std::basic_string_view<CharT> chars_to_trim = default_trim_arg<CharT>())
{
s.remove_prefix(std::min<size_t>(s.find_first_not_of(chars_to_trim), size(s)));
return s;
}
inline std::string_view right_trim(std::string_view s, const std::string_view chars_to_trim = default_trim_arg)
template<typename CharT>
inline std::basic_string_view<CharT> right_trim(std::basic_string_view<CharT> s, const std::basic_string_view<CharT> chars_to_trim = default_trim_arg<CharT>())
{
s.remove_suffix(std::min(size(s) - s.find_last_not_of(chars_to_trim) - 1, size(s)));
s.remove_suffix(std::min<size_t>(size(s) - s.find_last_not_of(chars_to_trim) - 1, size(s)));
return s;
}
inline std::string_view trim(std::string_view s, const std::string_view chars_to_trim = default_trim_arg)
template<typename CharT>
inline std::basic_string_view<CharT> trim(std::basic_string_view<CharT> s, const std::basic_string_view<CharT> chars_to_trim = default_trim_arg<CharT>())
{
return left_trim(right_trim(s, chars_to_trim), chars_to_trim);
}

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()
{

View File

@@ -51,7 +51,7 @@ namespace updating
{
auto current_version_to_next_version = VersionHelper{ VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION }.toWstring();
current_version_to_next_version += L" -> ";
current_version_to_next_version += info.version_string;
current_version_to_next_version += info.version.toWstring();
return current_version_to_next_version;
}
@@ -71,14 +71,14 @@ namespace updating
::notifications::show_toast_with_activations(std::move(contents),
TOAST_TITLE,
{},
{ ::notifications::link_button{ GITHUB_NEW_VERSION_UPDATE_NOW, L"powertoys://download_and_install_update/" }, ::notifications::link_button{ GITHUB_NEW_VERSION_MORE_INFO, info.release_page_uri.ToString().c_str() } },
{ ::notifications::link_button{ GITHUB_NEW_VERSION_UPDATE_NOW, L"powertoys://download_and_install_update/" }, ::notifications::link_button{ GITHUB_NEW_VERSION_MORE_INFO, info.release_page_url.ToString().c_str() } },
std::move(toast_params));
}
void show_download_start(const updating::new_version_download_info& info)
{
::notifications::progress_bar_params progress_bar_params;
std::wstring progress_title{ info.version_string };
std::wstring progress_title{ info.version.toWstring() };
progress_title += L' ';
progress_title += localized_strings::DOWNLOAD_IN_PROGRESS;
@@ -100,7 +100,7 @@ namespace updating
::notifications::show_toast_with_activations(std::move(contents),
TOAST_TITLE,
{},
{ ::notifications::link_button{ GITHUB_NEW_VERSION_VISIT, info.release_page_uri.ToString().c_str() } },
{ ::notifications::link_button{ GITHUB_NEW_VERSION_VISIT, info.release_page_url.ToString().c_str() } },
std::move(toast_params));
}
@@ -112,7 +112,7 @@ namespace updating
::notifications::show_toast_with_activations(std::move(contents),
TOAST_TITLE,
{},
{ ::notifications::link_button{ GITHUB_NEW_VERSION_VISIT, info.release_page_uri.ToString().c_str() } },
{ ::notifications::link_button{ GITHUB_NEW_VERSION_VISIT, info.release_page_url.ToString().c_str() } },
std::move(toast_params));
}
@@ -145,7 +145,7 @@ namespace updating
{
::notifications::progress_bar_params progress_bar_params;
std::wstring progress_title{ info.version_string };
std::wstring progress_title{ info.version.toWstring() };
progress_title += L' ';
progress_title += progress < 1 ? localized_strings::DOWNLOAD_IN_PROGRESS : localized_strings::DOWNLOAD_COMPLETE;
progress_bar_params.progress_title = progress_title;

View File

@@ -25,6 +25,7 @@ namespace
const wchar_t POWERTOYS_EXE_COMPONENT[] = L"{A2C66D91-3485-4D00-B04D-91844E6B345B}";
const wchar_t DONT_SHOW_AGAIN_RECORD_REGISTRY_PATH[] = L"delete_previous_powertoys_confirm";
const wchar_t LATEST_RELEASE_ENDPOINT[] = L"https://api.github.com/repos/microsoft/PowerToys/releases/latest";
const wchar_t ALL_RELEASES_ENDPOINT[] = L"https://api.github.com/repos/microsoft/PowerToys/releases";
const wchar_t MSIX_PACKAGE_NAME[] = L"Microsoft.PowerToys";
const wchar_t MSIX_PACKAGE_PUBLISHER[] = L"CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US";
@@ -101,55 +102,107 @@ namespace updating
return false;
}
std::future<std::optional<new_version_download_info>> get_new_github_version_info_async()
using winrt::Windows::Foundation::Uri;
std::optional<std::pair<Uri, std::wstring>> extract_installer_asset_download_info(const json::JsonObject& release_object)
{
try
{
http::HttpClient client;
const auto body = co_await client.request(winrt::Windows::Foundation::Uri{ LATEST_RELEASE_ENDPOINT });
auto json_body = json::JsonValue::Parse(body).GetObjectW();
auto new_version = json_body.GetNamedString(L"tag_name");
winrt::Windows::Foundation::Uri release_page_uri{ json_body.GetNamedString(L"html_url") };
VersionHelper github_version(winrt::to_string(new_version));
VersionHelper current_version(VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION);
if (github_version > current_version)
const std::wstring_view required_architecture = get_architecture_string(get_current_architecture());
constexpr const std::wstring_view required_filename_pattern = updating::INSTALLER_FILENAME_PATTERN;
// Desc-sorted by its priority
const std::array<std::wstring_view, 2> asset_extensions = { L".exe", L".msi" };
for (const auto asset_extension : asset_extensions)
{
const std::wstring_view required_architecture = get_architecture_string(get_current_architecture());
constexpr const std::wstring_view required_filename_pattern = updating::INSTALLER_FILENAME_PATTERN;
// Desc-sorted by its priority
const std::array<std::wstring_view, 2> asset_extensions = { L".exe", L".msi" };
for (const auto asset_extension : asset_extensions)
for (auto asset_elem : release_object.GetNamedArray(L"assets"))
{
for (auto asset_elem : json_body.GetNamedArray(L"assets"))
{
auto asset{ asset_elem.GetObjectW() };
std::wstring filename_lower = asset.GetNamedString(L"name", {}).c_str();
std::transform(begin(filename_lower), end(filename_lower), begin(filename_lower), ::towlower);
auto asset{ asset_elem.GetObjectW() };
std::wstring filename_lower = asset.GetNamedString(L"name", {}).c_str();
std::transform(begin(filename_lower), end(filename_lower), begin(filename_lower), ::towlower);
const bool extension_matched = filename_lower.ends_with(asset_extension);
const bool architecture_matched = filename_lower.find(required_architecture) != std::wstring::npos;
const bool filename_matched = filename_lower.find(required_filename_pattern) != std::wstring::npos;
if (extension_matched && architecture_matched && filename_matched)
{
winrt::Windows::Foundation::Uri msi_download_url{ asset.GetNamedString(L"browser_download_url") };
co_return new_version_download_info{ std::move(release_page_uri), new_version.c_str(), std::move(msi_download_url), std::move(filename_lower) };
}
const bool extension_matched = filename_lower.ends_with(asset_extension);
const bool architecture_matched = filename_lower.find(required_architecture) != std::wstring::npos;
const bool filename_matched = filename_lower.find(required_filename_pattern) != std::wstring::npos;
const bool asset_matched = extension_matched && architecture_matched && filename_matched;
if (extension_matched && architecture_matched && filename_matched)
{
return std::make_pair(Uri{ asset.GetNamedString(L"browser_download_url") }, std::move(filename_lower));
}
}
}
else
{
co_return std::nullopt;
}
}
catch (...)
{
co_return std::nullopt;
}
return std::nullopt;
}
std::optional<VersionHelper> extract_version_from_relase_object(const json::JsonObject& release_object)
{
try
{
return VersionHelper{ winrt::to_string(release_object.GetNamedString(L"tag_name")) };
}
catch (...)
{
}
return std::nullopt;
}
std::future<std::optional<new_version_download_info>> get_new_github_version_info_async(const bool prerelease)
{
try
{
http::HttpClient client;
json::JsonObject release_object;
const VersionHelper current_version(VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION);
VersionHelper github_version = current_version;
if (prerelease)
{
const auto body = co_await client.request(Uri{ ALL_RELEASES_ENDPOINT });
for (const auto& json : json::JsonValue::Parse(body).GetArray())
{
auto potential_release_object = json.GetObjectW();
const bool is_prerelease = potential_release_object.GetNamedBoolean(L"prerelease", false);
auto extracted_version = extract_version_from_relase_object(potential_release_object);
if (!is_prerelease || !extracted_version || *extracted_version <= github_version)
{
continue;
}
github_version = std::move(*extracted_version);
release_object = std::move(potential_release_object);
}
}
else
{
const auto body = co_await client.request(Uri{ LATEST_RELEASE_ENDPOINT });
release_object = json::JsonValue::Parse(body).GetObjectW();
auto extracted_version = extract_version_from_relase_object(release_object);
if (!extracted_version)
{
co_return std::nullopt;
}
github_version = std::move(*extracted_version);
}
if (github_version > current_version)
{
Uri release_page_url{ release_object.GetNamedString(L"html_url") };
auto installer_download_url = extract_installer_asset_download_info(release_object);
if (installer_download_url.has_value())
{
co_return new_version_download_info{ std::move(release_page_url),
std::move(github_version),
std::move(installer_download_url->first),
std::move(installer_download_url->second) };
}
}
}
catch (...)
{
}
co_return std::nullopt;
}
std::future<bool> uninstall_previous_msix_version_async()
{
winrt::Windows::Management::Deployment::PackageManager package_manager;

View File

@@ -20,13 +20,14 @@ namespace updating
struct new_version_download_info
{
winrt::Windows::Foundation::Uri release_page_uri;
std::wstring version_string;
winrt::Windows::Foundation::Uri release_page_url;
VersionHelper version;
winrt::Windows::Foundation::Uri installer_download_url;
std::wstring installer_filename;
};
std::future<std::optional<new_version_download_info>> get_new_github_version_info_async();
// TODO(yuyoyuppe): !! when merging to master, we must set the default value to false !!
std::future<std::optional<new_version_download_info>> get_new_github_version_info_async(const bool prerelease = true);
std::future<void> try_autoupdate(const bool download_updates_automatically);
std::filesystem::path get_pending_updates_path();

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.HideToolbarWhenUnmuted = new BoolProperty(true);
}
[JsonPropertyName("mute_camera_and_microphone_hotkey")]
public KeyboardKeysProperty MuteCameraAndMicrophoneHotkey { get; set; }
[JsonPropertyName("mute_microphone_hotkey")]
public KeyboardKeysProperty MuteMicrophoneHotkey { get; set; }
[JsonPropertyName("mute_camera_hotkey")]
public KeyboardKeysProperty MuteCameraHotkey { get; set; }
[JsonPropertyName("selected_camera")]
public StringProperty SelectedCamera { get; set; } = string.Empty;
[JsonPropertyName("toolbar_position")]
public StringProperty ToolbarPosition { get; set; } = "Top right corner";
[JsonPropertyName("toolbar_monitor")]
public StringProperty ToolbarMonitor { get; set; } = "Main monitor";
[JsonPropertyName("camera_overlay_image_path")]
public StringProperty CameraOverlayImagePath { get; set; } = string.Empty;
[JsonPropertyName("theme")]
public StringProperty Theme { get; set; }
[JsonPropertyName("hide_toolbar_when_unmuted")]
public BoolProperty HideToolbarWhenUnmuted { get; set; }
// converts the current to a json string.
public string ToJsonString()
{
return JsonSerializer.Serialize(this);
}
}
}

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

@@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Drawing;
using System.Runtime.CompilerServices;
using Microsoft.PowerToys.Settings.UI.Lib.Helpers;
using Microsoft.PowerToys.Settings.UI.Lib.ViewModels.Commands;
@@ -401,6 +402,7 @@ namespace Microsoft.PowerToys.Settings.UI.Lib.ViewModels
set
{
value = ToRGBHex(value);
if (!value.Equals(_zoneHighlightColor))
{
_zoneHighlightColor = value;
@@ -419,6 +421,7 @@ namespace Microsoft.PowerToys.Settings.UI.Lib.ViewModels
set
{
value = ToRGBHex(value);
if (!value.Equals(_zoneBorderColor, StringComparison.OrdinalIgnoreCase))
{
_zoneBorderColor = value;
@@ -437,6 +440,7 @@ namespace Microsoft.PowerToys.Settings.UI.Lib.ViewModels
set
{
value = ToRGBHex(value);
if (!value.Equals(_zoneInActiveColor))
{
_zoneInActiveColor = value;
@@ -524,5 +528,19 @@ namespace Microsoft.PowerToys.Settings.UI.Lib.ViewModels
SendConfigMSG(ipcMessage.ToJsonString());
}
}
private string ToRGBHex(string color)
{
try
{
int argb = int.Parse(color.Replace("#", string.Empty), System.Globalization.NumberStyles.HexNumber);
Color clr = Color.FromArgb(argb);
return "#" + clr.R.ToString("X2") + clr.G.ToString("X2") + clr.B.ToString("X2");
}
catch (Exception)
{
return "#FFFFFF";
}
}
}
}

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_ToolbarPosition.Header" xml:space="preserve">
<value>Toolbar postion</value>
</data>
<data name="VideoConference_ToolbarPosition_TopCenter.Content" xml:space="preserve">
<value>Top center</value>
</data>
<data name="VideoConference_ToolbarPosition_TopLeftCorner.Content" xml:space="preserve">
<value>Top left corner</value>
</data>
<data name="VideoConference_ToolbarPosition_TopRightCorner.Content" xml:space="preserve">
<value>Top right corner</value>
</data>
<data name="VideoConference_ToolbarPosition_BottomLeftCorner.Content" xml:space="preserve">
<value>Bottom left corner</value>
</data>
<data name="VideoConference_ToolbarPosition_BottomCenter.Content" xml:space="preserve">
<value>Bottom center</value>
</data>
<data name="VideoConference_ToolbarPosition_BottomRightCorner.Content" xml:space="preserve">
<value>Bottom right corner</value>
</data>
<data name="VideoConference_ToolbarMonitor.Header" xml:space="preserve">
<value>Show toolbar on</value>
</data>
<data name="VideoConference_ToolbarMonitor_Main.Content" xml:space="preserve">
<value>Main monitor</value>
</data>
<data name="VideoConference_ToolbarMonitor_UnderCursor.Content" xml:space="preserve">
<value>Monitor under cursor</value>
</data>
<data name="VideoConference_ToolbarMonitor_ActiveWindow.Content" xml:space="preserve">
<value>Active window monitor</value>
</data>
<data name="VideoConference_ToolbarMonitor_All.Content" xml:space="preserve">
<value>All monitors</value>
</data>
<data name="VideoConference_HideToolbarWhenUnmuted.Content" xml:space="preserve">
<value>Hide toolbar 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._hideToolbarWhenUnmuted = Settings.Properties.HideToolbarWhenUnmuted.Value;
switch (Settings.Properties.ToolbarPosition.Value)
{
case "Top left corner":
_toolbarPositionIndex = 0;
break;
case "Top center":
_toolbarPositionIndex = 1;
break;
case "Top right corner":
_toolbarPositionIndex = 2;
break;
case "Bottom left corner":
_toolbarPositionIndex = 3;
break;
case "Bottom center":
_toolbarPositionIndex = 4;
break;
case "Bottom right corner":
_toolbarPositionIndex = 5;
break;
}
switch (Settings.Properties.ToolbarMonitor.Value)
{
case "Main monitor":
_toolbarMonitorIndex = 0;
break;
case "All monitors":
_toolbarMonitorIndex = 1;
break;
}
}
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 _toolbarPositionIndex;
private int _toolbarMonitorIndex;
private HotkeySettings _cameraAndMicrophoneMuteHotkey;
private HotkeySettings _mirophoneMuteHotkey;
private HotkeySettings _cameraMuteHotkey;
private int _selectedCameraIndex = -1;
private bool _hideToolbarWhenUnmuted;
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 ToolbarPostionIndex
{
get
{
return _toolbarPositionIndex;
}
set
{
if (_toolbarPositionIndex != value)
{
_toolbarPositionIndex = value;
switch (_toolbarPositionIndex)
{
case 0:
Settings.Properties.ToolbarPosition.Value = "Top left corner";
RaisePropertyChanged();
break;
case 1:
Settings.Properties.ToolbarPosition.Value = "Top center";
RaisePropertyChanged();
break;
case 2:
Settings.Properties.ToolbarPosition.Value = "Top right corner";
RaisePropertyChanged();
break;
case 3:
Settings.Properties.ToolbarPosition.Value = "Bottom left corner";
RaisePropertyChanged();
break;
case 4:
Settings.Properties.ToolbarPosition.Value = "Bottom center";
RaisePropertyChanged();
break;
case 5:
Settings.Properties.ToolbarPosition.Value = "Bottom right corner";
RaisePropertyChanged();
break;
}
}
}
}
public int ToolbarMonitorIndex
{
get
{
return _toolbarMonitorIndex;
}
set
{
if (_toolbarMonitorIndex != value)
{
_toolbarMonitorIndex = value;
switch (_toolbarMonitorIndex)
{
case 0:
Settings.Properties.ToolbarMonitor.Value = "Main monitor";
RaisePropertyChanged();
break;
case 1:
Settings.Properties.ToolbarMonitor.Value = "All monitors";
RaisePropertyChanged();
break;
}
}
}
}
public bool HideToolbarWhenUnmuted
{
get
{
return _hideToolbarWhenUnmuted;
}
set
{
if (value != _hideToolbarWhenUnmuted)
{
_hideToolbarWhenUnmuted = value;
Settings.Properties.HideToolbarWhenUnmuted.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_ToolbarPosition"
MinWidth="240"
Margin="{StaticResource MediumTopMargin}"
SelectedIndex="{ Binding Mode=TwoWay, Path=ToolbarPostionIndex}"
IsEnabled="{ Binding Mode=TwoWay, Path=IsEnabled}">
<ComboBoxItem x:Uid="VideoConference_ToolbarPosition_TopLeftCorner"/>
<ComboBoxItem x:Uid="VideoConference_ToolbarPosition_TopCenter"/>
<ComboBoxItem x:Uid="VideoConference_ToolbarPosition_TopRightCorner"/>
<ComboBoxItem x:Uid="VideoConference_ToolbarPosition_BottomLeftCorner"/>
<ComboBoxItem x:Uid="VideoConference_ToolbarPosition_BottomCenter"/>
<ComboBoxItem x:Uid="VideoConference_ToolbarPosition_BottomRightCorner"/>
</ComboBox>
<ComboBox x:Uid="VideoConference_ToolbarMonitor"
MinWidth="240"
Margin="{StaticResource MediumTopMargin}"
SelectedIndex="{ Binding Mode=TwoWay, Path=ToolbarMonitorIndex}"
IsEnabled="{ Binding Mode=TwoWay, Path=IsEnabled}">
<ComboBoxItem x:Uid="VideoConference_ToolbarMonitor_Main"/>
<ComboBoxItem x:Uid="VideoConference_ToolbarMonitor_All"/>
</ComboBox>
<CheckBox x:Uid="VideoConference_HideToolbarWhenUnmuted"
IsChecked="{x:Bind Mode=TwoWay, Path=ViewModel.HideToolbarWhenUnmuted}"
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://aka.ms/PowerToysOverview_VideoConference">
<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

@@ -135,35 +135,17 @@ public:
IFACEMETHODIMP_(COLORREF)
GetZoneColor() noexcept
{
// Skip the leading # and convert to long
const auto color = m_settings->GetSettings()->zoneColor;
const auto tmp = std::stol(color.substr(1), nullptr, 16);
const auto nR = (tmp & 0xFF0000) >> 16;
const auto nG = (tmp & 0xFF00) >> 8;
const auto nB = (tmp & 0xFF);
return RGB(nR, nG, nB);
return (FancyZonesUtils::HexToRGB(m_settings->GetSettings()->zoneColor));
}
IFACEMETHODIMP_(COLORREF)
GetZoneBorderColor() noexcept
{
// Skip the leading # and convert to long
const auto color = m_settings->GetSettings()->zoneBorderColor;
const auto tmp = std::stol(color.substr(1), nullptr, 16);
const auto nR = (tmp & 0xFF0000) >> 16;
const auto nG = (tmp & 0xFF00) >> 8;
const auto nB = (tmp & 0xFF);
return RGB(nR, nG, nB);
return (FancyZonesUtils::HexToRGB(m_settings->GetSettings()->zoneBorderColor));
}
IFACEMETHODIMP_(COLORREF)
GetZoneHighlightColor() noexcept
{
// Skip the leading # and convert to long
const auto color = m_settings->GetSettings()->zoneHighlightColor;
const auto tmp = std::stol(color.substr(1), nullptr, 16);
const auto nR = (tmp & 0xFF0000) >> 16;
const auto nG = (tmp & 0xFF00) >> 8;
const auto nB = (tmp & 0xFF);
return RGB(nR, nG, nB);
return (FancyZonesUtils::HexToRGB(m_settings->GetSettings()->zoneHighlightColor));
}
IFACEMETHODIMP_(int)
GetZoneHighlightOpacity() noexcept

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

@@ -1,6 +1,7 @@
#pragma once
#include "gdiplus.h"
#include <common/string_utils.h>
namespace FancyZonesUtils
{
@@ -96,6 +97,24 @@ namespace FancyZonesUtils
SRCCOPY);
}
inline COLORREF HexToRGB(std::wstring_view hex, const COLORREF fallbackColor = RGB(255, 255, 255))
{
hex = left_trim<wchar_t>(trim<wchar_t>(hex), L"#");
try
{
const long long tmp = std::stoll(hex.data(), nullptr, 16);
const BYTE nR = (tmp & 0xFF0000) >> 16;
const BYTE nG = (tmp & 0xFF00) >> 8;
const BYTE nB = (tmp & 0xFF);
return RGB(nR, nG, nB);
}
catch (const std::exception&)
{
return fallbackColor;
}
}
inline void ParseDeviceId(PCWSTR deviceId, PWSTR parsedId, size_t size)
{
// We're interested in the unique part between the first and last #'s

View File

@@ -232,6 +232,27 @@ namespace FancyZonesUnitTests
CustomAssert::AreEqual(firstTime, monitorInfoCopy);
} while (next_permutation(monitorInfoPermutation.begin(), monitorInfoPermutation.end(), [](auto x, auto y) { return x.first < y.first; }));
}
TEST_METHOD(TestHexToRGB_rgb)
{
const auto expected = RGB(163, 246, 255);
const auto actual = HexToRGB(L"#A3F6FF");
Assert::AreEqual(expected, actual);
}
TEST_METHOD (TestHexToRGB_argb)
{
const auto expected = RGB(163, 246, 255);
const auto actual = HexToRGB(L"#FFA3F6FF");
Assert::AreEqual(expected, actual);
}
TEST_METHOD (TestHexToRGB_invalid)
{
const auto expected = RGB(255, 255, 255);
const auto actual = HexToRGB(L"zzz");
Assert::AreEqual(expected, actual);
}
};
}

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)
{
Toolbar::setMicrophoneMute(NotificationData->bMuted);
return S_OK;
}

View File

@@ -0,0 +1,23 @@
#pragma once
#include <mmdeviceapi.h>
#include <endpointvolume.h>
#include "Toolbar.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,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,311 @@
#include "pch.h"
#include "Toolbar.h"
#include <windowsx.h>
#include "common/windows_colors.h"
#include "VideoConferenceModule.h"
ToolbarImages Toolbar::darkImages;
ToolbarImages Toolbar::lightImages;
bool Toolbar::valueUpdated = false;
bool Toolbar::cameraMuted = false;
bool Toolbar::cameraInUse = false;
bool Toolbar::microphoneMuted = false;
std::wstring Toolbar::theme = L"system";
bool Toolbar::HideToolbarWhenUnmuted = true;
std::vector<HWND> Toolbar::hwnds;
UINT_PTR Toolbar::nTimerId;
unsigned __int64 Toolbar::lastTimeCamOrMicMuteStateChanged;
const int REFRESH_RATE = 100;
const int OVERLAY_SHOW_TIME = 500;
const int BORDER_OFFSET = 12;
Toolbar::Toolbar()
{
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 Toolbar::WindowProcessMessages(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
switch (msg)
{
case WM_DESTROY:
return 0;
case WM_LBUTTONDOWN:
{
int x = GET_X_LPARAM(lparam);
int y = GET_Y_LPARAM(lparam);
if (x < 322 / 2)
{
VideoConferenceModule::reverseMicrophoneMute();
}
else
{
VideoConferenceModule::reverseVirtualCameraMuteState();
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);
ToolbarImages* 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 || !HideToolbarWhenUnmuted)
{
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 Toolbar::show(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 Toolbar::hide()
{
for (auto& hwnd : hwnds)
{
PostMessage(hwnd, WM_CLOSE, 0, 0);
}
hwnds.clear();
}
bool Toolbar::getCameraMute()
{
return cameraMuted;
}
void Toolbar::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 Toolbar::getMicrophoneMute()
{
return microphoneMuted;
}
void Toolbar::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 Toolbar::setHideToolbarWhenUnmuted(bool hide)
{
HideToolbarWhenUnmuted = hide;
}
void Toolbar::setTheme(std::wstring theme)
{
Toolbar::theme = theme;
}

View File

@@ -0,0 +1,55 @@
#pragma once
#include <Windows.h>
#include <gdiplus.h>
#include "common/monitors.h"
struct ToolbarImages
{
Gdiplus::Image* camOnMicOn = nullptr;
Gdiplus::Image* camOffMicOn = nullptr;
Gdiplus::Image* camOnMicOff = nullptr;
Gdiplus::Image* camOffMicOff = nullptr;
Gdiplus::Image* camUnusedMicOn = nullptr;
Gdiplus::Image* camUnusedMicOff = nullptr;
};
class Toolbar
{
public:
Toolbar();
static void show(std::wstring position, std::wstring monitorString);
static void hide();
bool static getCameraMute();
void static setCameraMute(bool mute);
bool static getMicrophoneMute();
void static setMicrophoneMute(bool mute);
void static setTheme(std::wstring theme);
void static setHideToolbarWhenUnmuted(bool hide);
private:
static LRESULT CALLBACK WindowProcessMessages(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam);
// Window callback can't be non-static so this members can't as well
static std::vector<HWND> hwnds;
static ToolbarImages darkImages;
static ToolbarImages lightImages;
static bool valueUpdated;
static bool cameraMuted;
static bool cameraInUse;
static bool microphoneMuted;
static std::wstring theme;
static bool HideToolbarWhenUnmuted;
static unsigned __int64 lastTimeCamOrMicMuteStateChanged;
static UINT_PTR nTimerId;
};

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,15 @@
<?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>
<ItemGroup>
<ClCompile Include="trace.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="trace.h" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,102 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<ClCompile Include="CVolumeNotification.cpp" />
<ClCompile Include="dllmain.cpp" />
<ClCompile Include="Toolbar.cpp" />
<ClCompile Include="pch.cpp" />
<ClCompile Include="VideoConferenceModule.cpp" />
<ClCompile Include="trace.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="CVolumeNotification.h" />
<ClInclude Include="framework.h" />
<ClInclude Include="Toolbar.h" />
<ClInclude Include="pch.h" />
<ClInclude Include="VideoConferenceModule.h" />
<ClInclude Include="trace.h" />
</ItemGroup>
<ItemGroup>
<Image Include="black.bmp" />
<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="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>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
<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>
<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>
</ItemGroup>
<ItemGroup>
<Filter Include="Icons">
<UniqueIdentifier>{735361e2-82fa-4034-b9c9-cd6aa099eaa5}</UniqueIdentifier>
</Filter>
</ItemGroup>
</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"
#include "trace.h"
extern "C" IMAGE_DOS_HEADER __ImageBase;
VideoConferenceModule* instance = nullptr;
VideoConferenceSettings VideoConferenceModule::settings;
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)
{
settings.volumeNotification = new CVolumeNotification();
microphoneEndpoint->RegisterControlChangeNotify(settings.volumeNotification);
BOOL currentMute;
if (microphoneEndpoint->GetMute(&currentMute) == S_OK)
{
if (microphoneEndpoint->SetMute(!currentMute, NULL) == S_OK)
{
if (!currentMute)
{
Trace::MicrophoneMuted();
}
}
}
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)
{
settings.volumeNotification = new CVolumeNotification();
hr = endpointVolume->RegisterControlChangeNotify(settings.volumeNotification);
defaultDevice->Release();
defaultDevice = NULL;
endpointVolume->GetMute(&currentMute);
settings.volumeNotification->Release();
}
}
}
return currentMute;
}
void VideoConferenceModule::reverseVirtualCameraMuteState()
{
bool camera_muted = false;
if (!instance->_settingsUpdateChannel.has_value())
{
return;
}
instance->_settingsUpdateChannel->access([&camera_muted](auto settingsMemory) {
auto settings = reinterpret_cast<CameraSettingsUpdateChannel*>(settingsMemory._data);
settings->useOverlayImage = !settings->useOverlayImage;
camera_muted = settings->useOverlayImage;
});
if (camera_muted)
{
Trace::CameraMuted();
}
}
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, settings.cameraAndMicrophoneMuteHotkey))
{
reverseMicrophoneMute();
if (settings.toolbar.getCameraMute() != settings.toolbar.getMicrophoneMute())
{
reverseVirtualCameraMuteState();
settings.toolbar.setCameraMute(getVirtualCameraMuteState());
}
}
else if (isHotkeyPressed(kbd->vkCode, settings.microphoneMuteHotkey))
{
reverseMicrophoneMute();
}
else if (isHotkeyPressed(kbd->vkCode, settings.cameraMuteHotkey))
{
reverseVirtualCameraMuteState();
settings.toolbar.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();
settings.toolbar.show(settings.toolbarPositionString, settings.toolbarMonitorString);
}
inline VideoConferenceModule::~VideoConferenceModule()
{
if (settings.toolbar.getCameraMute())
{
reverseVirtualCameraMuteState();
settings.toolbar.setCameraMute(getVirtualCameraMuteState());
}
if (settings.toolbar.getMicrophoneMute())
{
reverseMicrophoneMute();
}
settings.toolbar.hide();
}
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"))
{
settings.cameraAndMicrophoneMuteHotkey = PowerToysSettings::HotkeyObject::from_json(*val);
}
if (const auto val = values.get_json(L"mute_microphone_hotkey"))
{
settings.microphoneMuteHotkey = PowerToysSettings::HotkeyObject::from_json(*val);
}
if (const auto val = values.get_json(L"mute_camera_hotkey"))
{
settings.cameraMuteHotkey = PowerToysSettings::HotkeyObject::from_json(*val);
}
if (const auto val = values.get_string_value(L"toolbar_position"))
{
settings.toolbarPositionString = val.value();
}
if (const auto val = values.get_string_value(L"toolbar_monitor"))
{
settings.toolbarMonitorString = val.value();
}
if (const auto val = values.get_string_value(L"selected_camera"); val && val != settings.selectedCamera)
{
settings.selectedCamera = val.value();
sendSourceCameraNameUpdate();
}
if (const auto val = values.get_string_value(L"camera_overlay_image_path"); val && val != settings.imageOverlayPath)
{
settings.imageOverlayPath = val.value();
sendOverlayImageUpdate();
}
if (const auto val = values.get_string_value(L"theme"))
{
Toolbar::setTheme(val.value());
}
if (const auto val = values.get_bool_value(L"hide_toolbar_when_unmuted"))
{
Toolbar::setHideToolbarWhenUnmuted(val.value());
}
settings.toolbar.show(settings.toolbarPositionString, settings.toolbarMonitorString);
}
}
catch (...)
{
// Improper JSON. TODO: handle the error.
}
}
void VideoConferenceModule::init_settings()
{
try
{
PowerToysSettings::PowerToyValues powerToysSettings = PowerToysSettings::PowerToyValues::load_from_settings_file(L"Video Conference");
if (const auto val = powerToysSettings.get_json(L"mute_camera_and_microphone_hotkey"))
{
settings.cameraAndMicrophoneMuteHotkey = PowerToysSettings::HotkeyObject::from_json(*val);
}
if (const auto val = powerToysSettings.get_json(L"mute_microphone_hotkey"))
{
settings.microphoneMuteHotkey = PowerToysSettings::HotkeyObject::from_json(*val);
}
if (const auto val = powerToysSettings.get_json(L"mute_camera_hotkey"))
{
settings.cameraMuteHotkey = PowerToysSettings::HotkeyObject::from_json(*val);
}
if (const auto val = powerToysSettings.get_string_value(L"toolbar_position"))
{
settings.toolbarPositionString = val.value();
}
if (const auto val = powerToysSettings.get_string_value(L"toolbar_monitor"))
{
settings.toolbarMonitorString = val.value();
}
if (const auto val = powerToysSettings.get_string_value(L"selected_camera"))
{
settings.selectedCamera = val.value();
}
if (const auto val = powerToysSettings.get_string_value(L"camera_overlay_image_path"))
{
settings.imageOverlayPath = val.value();
}
if (const auto val = powerToysSettings.get_string_value(L"theme"))
{
Toolbar::setTheme(val.value());
}
if (const auto val = powerToysSettings.get_bool_value(L"hide_toolbar_when_unmuted"))
{
Toolbar::setHideToolbarWhenUnmuted(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)
{
settings.toolbar.setMicrophoneMute(getMicrophoneMuteState());
settings.toolbar.setCameraMute(getVirtualCameraMuteState());
settings.toolbar.show(settings.toolbarPositionString, settings.toolbarMonitorString);
_enabled = true;
#if defined(DISABLE_LOWLEVEL_HOOKS_WHEN_DEBUGGED)
if (IsDebuggerPresent())
{
return;
}
#endif
hook_handle = SetWindowsHookEx(WH_KEYBOARD_LL, LowLevelKeyboardProc, GetModuleHandle(NULL), NULL);
}
}
void VideoConferenceModule::disable()
{
if (_enabled)
{
if (hook_handle)
{
bool success = UnhookWindowsHookEx(hook_handle);
if (success)
{
hook_handle = nullptr;
}
}
if (settings.toolbar.getCameraMute())
{
reverseVirtualCameraMuteState();
settings.toolbar.setCameraMute(getVirtualCameraMuteState());
}
if (settings.toolbar.getMicrophoneMute())
{
reverseMicrophoneMute();
}
settings.toolbar.hide();
_enabled = false;
}
}
bool VideoConferenceModule::is_enabled()
{
return _enabled;
}
void VideoConferenceModule::destroy()
{
delete this;
instance = nullptr;
}
void VideoConferenceModule::sendSourceCameraNameUpdate()
{
if (!_settingsUpdateChannel.has_value() || settings.selectedCamera.empty())
{
return;
}
_settingsUpdateChannel->access([](auto memory) {
auto updatesChannel = reinterpret_cast<CameraSettingsUpdateChannel*>(memory._data);
updatesChannel->sourceCameraName.emplace();
std::copy(begin(settings.selectedCamera), end(settings.selectedCamera), begin(*updatesChannel->sourceCameraName));
});
}
void VideoConferenceModule::sendOverlayImageUpdate()
{
if (!_settingsUpdateChannel.has_value())
{
return;
}
_imageOverlayChannel.reset();
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(),
settings.imageOverlayPath != L"" ? settings.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,74 @@
#pragma once
#include <mmdeviceapi.h>
#include <endpointvolume.h>
#include <interface/powertoy_module_interface.h>
#include "common/settings_objects.h"
#include "Toolbar.h"
#include "CVolumeNotification.h"
#include <SerializedSharedMemory.h>
extern class VideoConferenceModule* instance;
struct VideoConferenceSettings
{
Toolbar toolbar;
CVolumeNotification* volumeNotification = nullptr;
PowerToysSettings::HotkeyObject cameraAndMicrophoneMuteHotkey = PowerToysSettings::HotkeyObject::from_settings(true, false, false, false, 78);
PowerToysSettings::HotkeyObject microphoneMuteHotkey = PowerToysSettings::HotkeyObject::from_settings(true, false, false, true, 65);
PowerToysSettings::HotkeyObject cameraMuteHotkey = PowerToysSettings::HotkeyObject::from_settings(true, false, false, true, 79);
std::wstring toolbarPositionString;
std::wstring toolbarMonitorString;
std::wstring selectedCamera;
std::wstring imageOverlayPath;
};
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 VideoConferenceSettings settings;
};

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="Toolbar.h" />
<ClInclude Include="pch.h" />
<ClInclude Include="VideoConferenceModule.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="CVolumeNotification.cpp" />
<ClCompile Include="dllmain.cpp" />
<ClCompile Include="Toolbar.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,35 @@
// dllmain.cpp : Defines the entry point for the DLL application.
#include "pch.h"
#include <interface/powertoy_module_interface.h>
#include "trace.h"
#include "VideoConferenceModule.h"
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
Trace::RegisterProvider();
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
Trace::UnregisterProvider();
break;
}
return TRUE;
}
extern "C" __declspec(dllexport) PowertoyModuleIface* __cdecl powertoy_create()
{
if (!instance)
{
instance = new VideoConferenceModule();
return instance;
}
else
{
return nullptr;
}
}

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>

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