Compare commits
22 Commits
shawn/comS
...
v0.24.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d4e0265821 | ||
|
|
6e92e20884 | ||
|
|
8611a9a29a | ||
|
|
d2913bc249 | ||
|
|
9ae3efa98e | ||
|
|
128a305ae4 | ||
|
|
c48424a3eb | ||
|
|
813fe15f52 | ||
|
|
a2b30209f4 | ||
|
|
c958a4a95e | ||
|
|
d4db76a859 | ||
|
|
3386b953bb | ||
|
|
d9dd1e1649 | ||
|
|
3ba6a48157 | ||
|
|
e9a7b5d08e | ||
|
|
2b0896dded | ||
|
|
d246bdd32f | ||
|
|
7116d69002 | ||
|
|
d8f0ebe105 | ||
|
|
2946b08823 | ||
|
|
7bcbda39ef | ||
|
|
b90363a041 |
7
.pipelines/build-videoconference.cmd
Normal file
@@ -0,0 +1,7 @@
|
||||
cd /D "%~dp0"
|
||||
|
||||
call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\Tools\VsDevCmd.bat" -arch=amd64 -host_arch=amd64 -winsdk=10.0.18362.0
|
||||
call msbuild -m ../PowerToys.sln /t:modules\VideoConference\VideoConferenceCustomMediaSource,modules\VideoConference\VideoConferenceVirtualDriver /p:Configuration=Release /p:Platform=x64 /p:CIBuild=true || exit /b 1
|
||||
SET PTRoot=..
|
||||
|
||||
|
||||
100
.pipelines/pipeline.user.windows.videoconference.yml
Normal file
@@ -0,0 +1,100 @@
|
||||
environment:
|
||||
host:
|
||||
os: 'windows'
|
||||
flavor: 'server'
|
||||
version: '2019'
|
||||
runtime:
|
||||
provider: 'appcontainer'
|
||||
image: 'cdpxwin1809.azurecr.io/global/vse2019:latest'
|
||||
source_mode: 'map'
|
||||
|
||||
version:
|
||||
name: 'PowerToys'
|
||||
major: 1
|
||||
minor: 0
|
||||
|
||||
signing_options:
|
||||
profile: 'external_distribution'
|
||||
|
||||
package_sources:
|
||||
nuget:
|
||||
feeds:
|
||||
'Nuget': 'https://api.nuget.org/v3/index.json'
|
||||
'Toolset': 'https://msazure.pkgs.visualstudio.com/_packaging/Toolset/nuget/v3/index.json'
|
||||
'PipelineBuildSupplement': 'https://msazure.pkgs.visualstudio.com/_packaging/PipelineBuildSupplement/nuget/v3/index.json'
|
||||
|
||||
restore:
|
||||
commands:
|
||||
- !!defaultcommand
|
||||
name: 'Restore Power Toys Telemetry'
|
||||
command: '.pipelines\restore-telemetry.cmd'
|
||||
- !!defaultcommand
|
||||
name: 'Restore Power Toys'
|
||||
command: '.pipelines\restore.cmd'
|
||||
- !!defaultcommand
|
||||
name: 'Restore Localization packages'
|
||||
command: '.pipelines\restore-localization.cmd'
|
||||
|
||||
|
||||
build:
|
||||
commands:
|
||||
- !!buildcommand
|
||||
name: 'Build Power Toys'
|
||||
command: '.pipelines\build-videoconference.cmd'
|
||||
artifacts:
|
||||
- to: 'Symbols'
|
||||
include:
|
||||
- 'x64/**/*.pdb'
|
||||
exclude:
|
||||
- 'x64/Release/obj/**/*.pdb'
|
||||
- from: 'x64/Release'
|
||||
to: 'Build_Output'
|
||||
include:
|
||||
- 'modules\VideoConference\VideoConferenceModule.dll'
|
||||
- 'modules\VideoConference\VideoConferenceVirtualDriver\VideoConferenceVirtualDriver.dll'
|
||||
- 'modules\VideoConference\VideoConferenceVirtualDriver\VideoConferenceCustomMediaSource.dll'
|
||||
- 'modules\VideoConference\VideoConferenceVirtualDriver\videoconferencevirtualdriver.cat'
|
||||
- 'modules\VideoConference\VideoConferenceVirtualDriver\VideoConferenceVirtualDriver.inf'
|
||||
- 'modules\VideoConference\VideoConferenceVirtualDriver\VideoConferenceVirtualDriver.cab'
|
||||
signing_options:
|
||||
sign_inline: true # This does signing a soon as this command completes
|
||||
- !!buildcommand
|
||||
name: 'Localize Power Toys'
|
||||
command: '.pipelines\build-localization.cmd'
|
||||
artifacts:
|
||||
- from: 'out\loc'
|
||||
to: 'loc'
|
||||
include:
|
||||
- '**/*'
|
||||
|
||||
|
||||
#package:
|
||||
# commands:
|
||||
# - !!buildcommand
|
||||
# name: 'Build MSIX package'
|
||||
# command: 'installer\msix\build_msix_cdpx.cmd'
|
||||
# artifacts:
|
||||
# - from: 'installer\msix\bin'
|
||||
# to: 'Build_MSIX_Package_Output'
|
||||
# include:
|
||||
# - '*.msix'
|
||||
# - '*.msixbundle'
|
||||
# signing_options:
|
||||
# profile: '400'
|
||||
|
||||
static_analysis_options:
|
||||
binskim_options:
|
||||
files_to_scan:
|
||||
- from: 'installer/packages'
|
||||
exclude:
|
||||
- 'WiX.*/**/*.dll'
|
||||
- 'Wix.*/**/*.exe'
|
||||
moderncop_options:
|
||||
files_to_scan:
|
||||
- from: 'src'
|
||||
exclude:
|
||||
- '**/just.config.js'
|
||||
- '**/webpack.config.js'
|
||||
- '**/webpack.serve.config.js'
|
||||
- '**/dist/bundle.js'
|
||||
|
||||
@@ -116,6 +116,12 @@ 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'
|
||||
- 'modules\VideoConference\VideoConferenceVirtualDriver\VideoConferenceVirtualDriver.cab'
|
||||
- 'Notifications.dll'
|
||||
- 'os-detection.dll'
|
||||
- 'PowerToys.exe'
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -267,6 +267,19 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.PowerToys.Setting
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Plugin.Calculator.UnitTest", "src\modules\launcher\Plugins\Microsoft.Plugin.Calculator.UnitTest\Microsoft.Plugin.Calculator.UnitTest.csproj", "{632BBE62-5421-49EA-835A-7FFA4F499BD6}"
|
||||
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
|
||||
@@ -533,6 +546,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
|
||||
{632BBE62-5421-49EA-835A-7FFA4F499BD6}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{632BBE62-5421-49EA-835A-7FFA4F499BD6}.Debug|x64.Build.0 = Debug|x64
|
||||
{632BBE62-5421-49EA-835A-7FFA4F499BD6}.Release|x64.ActiveCfg = Release|x64
|
||||
@@ -613,6 +644,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}
|
||||
{632BBE62-5421-49EA-835A-7FFA4F499BD6} = {4AFC9975-2456-4C70-94A4-84073C1CED93}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
@@ -666,7 +762,7 @@
|
||||
<File Source="$(var.BinX64Dir)SettingsUIRunner\Microsoft.PowerToys.Settings.UI.Runner.exe"/>
|
||||
<File Source="$(var.BinX64Dir)SettingsUIRunner\Microsoft.PowerToys.Settings.UI.exe"/>
|
||||
<!-- dll -->
|
||||
<?foreach File in concrt140_app.dll;Microsoft.Bcl.AsyncInterfaces.dll;Microsoft.PowerToys.Settings.UI.Lib.dll;Microsoft.PowerToys.Settings.UI.Runner.dll;Microsoft.Toolkit.dll;Microsoft.Toolkit.Uwp.dll;Microsoft.Toolkit.Uwp.UI.dll;Microsoft.Toolkit.Win32.UI.XamlHost.dll;Microsoft.Toolkit.Win32.UI.XamlHost.Managed.dll;Microsoft.Toolkit.Wpf.UI.Controls.dll;Microsoft.Toolkit.Wpf.UI.XamlHost.dll;Microsoft.UI.Xaml.dll;Microsoft.Xaml.Interactions.dll;Microsoft.Xaml.Interactivity.dll;msvcp140_1_app.dll;msvcp140_2_app.dll;msvcp140_app.dll;Newtonsoft.Json.dll;PowerToysInterop.dll;System.Runtime.CompilerServices.Unsafe.dll;System.Text.Encodings.Web.dll;System.Text.Json.dll;vcamp140_app.dll;vccorlib140_app.dll;vcomp140_app.dll;vcruntime140_1_app.dll;vcruntime140_app.dll;Telemetry.dll;ManagedCommon.dll?>
|
||||
<?foreach File in concrt140_app.dll;Microsoft.Bcl.AsyncInterfaces.dll;Microsoft.PowerToys.Settings.UI.Lib.dll;Microsoft.PowerToys.Settings.UI.Runner.dll;Microsoft.Toolkit.dll;Microsoft.Toolkit.Uwp.dll;Microsoft.Toolkit.Uwp.UI.dll;Microsoft.Toolkit.Win32.UI.XamlHost.dll;Microsoft.Toolkit.Win32.UI.XamlHost.Managed.dll;Microsoft.Toolkit.Wpf.UI.Controls.dll;Microsoft.Toolkit.Wpf.UI.XamlHost.dll;Microsoft.UI.Xaml.dll;Microsoft.Xaml.Interactions.dll;Microsoft.Xaml.Interactivity.dll;msvcp140_1_app.dll;msvcp140_2_app.dll;msvcp140_app.dll;Newtonsoft.Json.dll;PowerToysInterop.dll;System.Runtime.CompilerServices.Unsafe.dll;System.Text.Encodings.Web.dll;System.Text.Json.dll;vcamp140_app.dll;vccorlib140_app.dll;vcomp140_app.dll;vcruntime140_1_app.dll;vcruntime140_app.dll;Telemetry.dll;ManagedCommon.dll;System.Deployment.dll;System.Windows.Forms.dll;System.Runtime.Serialization.Formatters.Soap.dll?>
|
||||
<File Id="SettingsV2_$(var.File)" Source="$(var.BinX64Dir)SettingsUIRunner\$(var.File)" />
|
||||
<?endforeach?>
|
||||
<!-- json -->
|
||||
@@ -706,7 +802,7 @@
|
||||
</DirectoryRef>
|
||||
<DirectoryRef Id="SettingsV2AssetsModulesInstallFolder" FileSource="$(var.BinX64Dir)SettingsUIRunner\Assets\Modules">
|
||||
<Component Id="SettingsV2AssetsModules" Guid="A0B961A9-77D0-4223-88A9-E3B41BD9C329" Win64="yes">
|
||||
<?foreach File in ColorPicker.png;FancyZones.png;ImageResizer.png;KBM.png;PowerLauncher.png;PowerPreview.png;PowerRename.png;PT.png;ShortcutGuide.png?>
|
||||
<?foreach File in ColorPicker.png;FancyZones.png;ImageResizer.png;KBM.png;PowerLauncher.png;PowerPreview.png;PowerRename.png;PT.png;ShortcutGuide.png;VideoConference.png?>
|
||||
<File Source="$(var.BinX64Dir)SettingsUIRunner\Assets\Modules\$(var.File)" />
|
||||
<?endforeach?>
|
||||
</Component>
|
||||
@@ -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" />
|
||||
|
||||
@@ -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\
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -12,4 +12,7 @@ EXPORTS
|
||||
TelemetryLogUninstallFailCA
|
||||
TelemetryLogRepairCancelCA
|
||||
TelemetryLogRepairFailCA
|
||||
TerminateProcessesCA
|
||||
TerminateProcessesCA
|
||||
CertifyVirtualCameraDriverCA
|
||||
InstallVirtualCameraDriverCA
|
||||
UninstallVirtualCameraDriverCA
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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.2</Version>
|
||||
<Version>0.24.0</Version>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
|
||||
142
src/common/MicrophoneDevice.cpp
Normal file
@@ -0,0 +1,142 @@
|
||||
#include "pch.h"
|
||||
#include "MicrophoneDevice.h"
|
||||
|
||||
#include <Functiondiscoverykeys_devpkey.h>
|
||||
|
||||
MicrophoneDevice::MicrophoneDevice(wil::com_ptr_nothrow<IMMDevice> device, wil::com_ptr_nothrow<IAudioEndpointVolume> endpoint) :
|
||||
_device{ std::move(device) },
|
||||
_endpoint{ std::move(endpoint) }
|
||||
{
|
||||
if (!_device || !_endpoint)
|
||||
{
|
||||
throw std::logic_error("MicrophoneDevice was initialized with null objects!");
|
||||
}
|
||||
_device->GetId(&_id);
|
||||
wil::com_ptr_nothrow<IPropertyStore> props;
|
||||
_device->OpenPropertyStore(
|
||||
STGM_READ, &props);
|
||||
if (props)
|
||||
{
|
||||
props->GetValue(PKEY_Device_FriendlyName, &_friendly_name);
|
||||
}
|
||||
}
|
||||
|
||||
MicrophoneDevice::~MicrophoneDevice()
|
||||
{
|
||||
if (_notifier)
|
||||
{
|
||||
_endpoint->UnregisterControlChangeNotify(_notifier.get());
|
||||
}
|
||||
}
|
||||
|
||||
bool MicrophoneDevice::active() const noexcept
|
||||
{
|
||||
DWORD state = 0;
|
||||
_device->GetState(&state);
|
||||
return state == DEVICE_STATE_ACTIVE;
|
||||
}
|
||||
|
||||
void MicrophoneDevice::set_muted(const bool muted) noexcept
|
||||
{
|
||||
_endpoint->SetMute(muted, nullptr);
|
||||
}
|
||||
|
||||
bool MicrophoneDevice::muted() const noexcept
|
||||
{
|
||||
BOOL muted = FALSE;
|
||||
_endpoint->GetMute(&muted);
|
||||
return muted;
|
||||
}
|
||||
|
||||
void MicrophoneDevice::toggle_muted() noexcept
|
||||
{
|
||||
set_muted(!muted());
|
||||
}
|
||||
|
||||
std::wstring_view MicrophoneDevice::id() const noexcept
|
||||
{
|
||||
return _id ? _id.get() : FALLBACK_ID;
|
||||
}
|
||||
|
||||
std::wstring_view MicrophoneDevice::name() const noexcept
|
||||
{
|
||||
return _friendly_name.pwszVal ? _friendly_name.pwszVal : FALLBACK_NAME;
|
||||
}
|
||||
|
||||
void MicrophoneDevice::set_mute_changed_callback(mute_changed_cb_t callback) noexcept
|
||||
{
|
||||
_mute_changed_callback = std::move(callback);
|
||||
_notifier = winrt::make<VolumeNotifier>(this);
|
||||
|
||||
_endpoint->RegisterControlChangeNotify(_notifier.get());
|
||||
}
|
||||
|
||||
std::optional<MicrophoneDevice> MicrophoneDevice::getDefault()
|
||||
{
|
||||
auto deviceEnumerator = wil::CoCreateInstanceNoThrow<MMDeviceEnumerator, IMMDeviceEnumerator>();
|
||||
if (!deviceEnumerator)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
wil::com_ptr_nothrow<IMMDevice> captureDevice;
|
||||
deviceEnumerator->GetDefaultAudioEndpoint(eCapture, eCommunications, &captureDevice);
|
||||
if (!captureDevice)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
wil::com_ptr_nothrow<IAudioEndpointVolume> microphoneEndpoint;
|
||||
captureDevice->Activate(__uuidof(IAudioEndpointVolume), CLSCTX_INPROC_SERVER, nullptr, reinterpret_cast<LPVOID*>(µphoneEndpoint));
|
||||
if (!microphoneEndpoint)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
return std::make_optional<MicrophoneDevice>(std::move(captureDevice), std::move(microphoneEndpoint));
|
||||
}
|
||||
|
||||
std::vector<MicrophoneDevice> MicrophoneDevice::getAllActive()
|
||||
{
|
||||
std::vector<MicrophoneDevice> microphoneDevices;
|
||||
auto deviceEnumerator = wil::CoCreateInstanceNoThrow<MMDeviceEnumerator, IMMDeviceEnumerator>();
|
||||
if (!deviceEnumerator)
|
||||
{
|
||||
return microphoneDevices;
|
||||
}
|
||||
|
||||
wil::com_ptr_nothrow<IMMDeviceCollection> captureDevices;
|
||||
deviceEnumerator->EnumAudioEndpoints(eCapture, DEVICE_STATE_ACTIVE, &captureDevices);
|
||||
if (!captureDevices)
|
||||
{
|
||||
return microphoneDevices;
|
||||
}
|
||||
UINT nDevices = 0;
|
||||
captureDevices->GetCount(&nDevices);
|
||||
microphoneDevices.reserve(nDevices);
|
||||
for (UINT i = 0; i < nDevices; ++i)
|
||||
{
|
||||
wil::com_ptr_nothrow<IMMDevice> device;
|
||||
captureDevices->Item(i, &device);
|
||||
if (!device)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
wil::com_ptr_nothrow<IAudioEndpointVolume> microphoneEndpoint;
|
||||
device->Activate(__uuidof(IAudioEndpointVolume), CLSCTX_INPROC_SERVER, nullptr, reinterpret_cast<LPVOID*>(µphoneEndpoint));
|
||||
if (!microphoneEndpoint)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
microphoneDevices.emplace_back(std::move(device), std::move(microphoneEndpoint));
|
||||
}
|
||||
return microphoneDevices;
|
||||
}
|
||||
|
||||
MicrophoneDevice::VolumeNotifier::VolumeNotifier(MicrophoneDevice* subscribedDevice) :
|
||||
_subscribedDevice{ subscribedDevice }
|
||||
{
|
||||
}
|
||||
|
||||
HRESULT __stdcall MicrophoneDevice::VolumeNotifier::OnNotify(PAUDIO_VOLUME_NOTIFICATION_DATA data)
|
||||
{
|
||||
_subscribedDevice->_mute_changed_callback(data->bMuted);
|
||||
return S_OK;
|
||||
}
|
||||
60
src/common/MicrophoneDevice.h
Normal file
@@ -0,0 +1,60 @@
|
||||
#pragma once
|
||||
#include <winrt/base.h>
|
||||
#include <wil/resource.h>
|
||||
#include <wil/com.h>
|
||||
|
||||
#include <Windows.h>
|
||||
#include <Unknwn.h>
|
||||
|
||||
#include <string_view>
|
||||
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
|
||||
#include <Mmdeviceapi.h>
|
||||
#include <Endpointvolume.h>
|
||||
|
||||
class MicrophoneDevice
|
||||
{
|
||||
public:
|
||||
using mute_changed_cb_t = std::function<void(bool muted)>;
|
||||
|
||||
private:
|
||||
friend struct VolumeNotifier;
|
||||
|
||||
struct VolumeNotifier : winrt::implements<VolumeNotifier, IAudioEndpointVolumeCallback>
|
||||
{
|
||||
MicrophoneDevice* _subscribedDevice = nullptr;
|
||||
VolumeNotifier(MicrophoneDevice* subscribedDevice);
|
||||
|
||||
virtual HRESULT __stdcall OnNotify(PAUDIO_VOLUME_NOTIFICATION_DATA data) override;
|
||||
};
|
||||
|
||||
wil::unique_cotaskmem_string _id;
|
||||
wil::unique_prop_variant _friendly_name;
|
||||
mute_changed_cb_t _mute_changed_callback;
|
||||
winrt::com_ptr<IAudioEndpointVolumeCallback> _notifier;
|
||||
wil::com_ptr_nothrow<IAudioEndpointVolume> _endpoint;
|
||||
wil::com_ptr_nothrow<IMMDevice> _device;
|
||||
|
||||
constexpr static inline std::wstring_view FALLBACK_NAME = L"Unknown device";
|
||||
constexpr static inline std::wstring_view FALLBACK_ID = L"UNKNOWN_ID";
|
||||
|
||||
public:
|
||||
MicrophoneDevice(MicrophoneDevice&&) noexcept = default;
|
||||
MicrophoneDevice(wil::com_ptr_nothrow<IMMDevice> device, wil::com_ptr_nothrow<IAudioEndpointVolume> endpoint);
|
||||
~MicrophoneDevice();
|
||||
|
||||
bool active() const noexcept;
|
||||
void set_muted(const bool muted) noexcept;
|
||||
bool muted() const noexcept;
|
||||
void toggle_muted() noexcept;
|
||||
|
||||
std::wstring_view id() const noexcept;
|
||||
std::wstring_view name() const noexcept;
|
||||
void set_mute_changed_callback(mute_changed_cb_t callback) noexcept;
|
||||
|
||||
static std::optional<MicrophoneDevice> getDefault();
|
||||
static std::vector<MicrophoneDevice> getAllActive();
|
||||
};
|
||||
88
src/common/VideoCaptureDeviceList.cpp
Normal file
@@ -0,0 +1,88 @@
|
||||
#include "pch.h"
|
||||
#include "VideoCaptureDeviceList.h"
|
||||
#include <mfapi.h>
|
||||
#include <Mfidl.h>
|
||||
|
||||
void VideoCaptureDeviceList::Clear()
|
||||
{
|
||||
for (UINT32 i = 0; i < m_numberDevices; i++)
|
||||
{
|
||||
CoTaskMemFree(m_deviceFriendlyNames[i]);
|
||||
if (m_ppDevices[i])
|
||||
{
|
||||
m_ppDevices[i]->Release();
|
||||
}
|
||||
}
|
||||
CoTaskMemFree(m_ppDevices);
|
||||
m_ppDevices = nullptr;
|
||||
if (m_deviceFriendlyNames)
|
||||
{
|
||||
delete[] m_deviceFriendlyNames;
|
||||
}
|
||||
|
||||
m_deviceFriendlyNames = nullptr;
|
||||
m_numberDevices = 0;
|
||||
}
|
||||
|
||||
HRESULT VideoCaptureDeviceList::EnumerateDevices()
|
||||
{
|
||||
HRESULT hr = S_OK;
|
||||
wil::com_ptr<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 VideoCaptureDeviceList::GetDevice(UINT32 index, IMFActivate** ppActivate)
|
||||
{
|
||||
if (index >= Count())
|
||||
{
|
||||
return E_INVALIDARG;
|
||||
}
|
||||
|
||||
*ppActivate = m_ppDevices[index];
|
||||
(*ppActivate)->AddRef();
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
std::wstring_view VideoCaptureDeviceList::GetDeviceName(UINT32 index)
|
||||
{
|
||||
if (index >= Count())
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
return m_deviceFriendlyNames[index];
|
||||
}
|
||||
33
src/common/VideoCaptureDeviceList.h
Normal file
@@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
#include <Windows.h>
|
||||
#include <mfobjects.h>
|
||||
#include <string_view>
|
||||
|
||||
class VideoCaptureDeviceList
|
||||
{
|
||||
UINT32 m_numberDevices;
|
||||
// TODO: use wil
|
||||
IMFActivate** m_ppDevices = nullptr;
|
||||
wchar_t** m_deviceFriendlyNames = nullptr;
|
||||
|
||||
public:
|
||||
VideoCaptureDeviceList() :
|
||||
m_ppDevices(NULL), m_numberDevices(0)
|
||||
{
|
||||
}
|
||||
~VideoCaptureDeviceList()
|
||||
{
|
||||
Clear();
|
||||
}
|
||||
|
||||
UINT32 Count() const { return m_numberDevices; }
|
||||
|
||||
void Clear();
|
||||
HRESULT EnumerateDevices();
|
||||
HRESULT GetDevice(UINT32 index, IMFActivate** ppActivate);
|
||||
std::wstring_view GetDeviceName(UINT32 index);
|
||||
};
|
||||
@@ -95,19 +95,23 @@
|
||||
<ClInclude Include="..\common.h" />
|
||||
<ClInclude Include="..\keyboard_layout.h" />
|
||||
<ClInclude Include="..\keyboard_layout_impl.h" />
|
||||
<ClInclude Include="..\MicrophoneDevice.h" />
|
||||
<ClInclude Include="..\os-detect.h" />
|
||||
<ClInclude Include="..\pch.h" />
|
||||
<ClInclude Include="..\two_way_pipe_message_ipc.h" />
|
||||
<ClInclude Include="..\two_way_pipe_message_ipc_impl.h" />
|
||||
<ClInclude Include="..\VideoCaptureDeviceList.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="..\common.cpp" />
|
||||
<ClCompile Include="..\keyboard_layout.cpp" />
|
||||
<ClCompile Include="..\MicrophoneDevice.cpp" />
|
||||
<ClCompile Include="..\os-detect.cpp" />
|
||||
<ClCompile Include="..\pch.cpp">
|
||||
<PrecompiledHeader Condition="'$(CIBuild)'!='true'">Create</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\two_way_pipe_message_ipc.cpp" />
|
||||
<ClCompile Include="..\VideoCaptureDeviceList.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
|
||||
@@ -36,6 +36,12 @@
|
||||
<ClInclude Include="..\os-detect.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\MicrophoneDevice.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\VideoCaptureDeviceList.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="..\keyboard_layout.cpp">
|
||||
@@ -53,6 +59,12 @@
|
||||
<ClCompile Include="..\os-detect.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\MicrophoneDevice.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\VideoCaptureDeviceList.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
|
||||
@@ -49,7 +49,7 @@ bool is_system_window(HWND hwnd, const char* class_name)
|
||||
|
||||
int run_message_loop(const bool until_idle, const std::optional<uint32_t> timeout_seconds)
|
||||
{
|
||||
MSG msg;
|
||||
MSG msg{};
|
||||
bool stop = false;
|
||||
UINT_PTR timerId = 0;
|
||||
if (timeout_seconds.has_value())
|
||||
|
||||
@@ -91,6 +91,7 @@
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
|
||||
<MultiProcessorCompilation Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</MultiProcessorCompilation>
|
||||
<ConformanceMode Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ConformanceMode>
|
||||
</ClCompile>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
@@ -106,6 +107,7 @@
|
||||
<AdditionalIncludeDirectories>inc;telemetry;..\..\deps\cpprestsdk\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<AdditionalOptions>/await %(AdditionalOptions)</AdditionalOptions>
|
||||
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
@@ -132,6 +134,8 @@
|
||||
<ClInclude Include="keyboard_layout.h" />
|
||||
<ClInclude Include="keyboard_layout_impl.h" />
|
||||
<ClInclude Include="LowlevelKeyboardEvent.h" />
|
||||
<ClInclude Include="MicrophoneDevice.h" />
|
||||
<ClInclude Include="naming.h" />
|
||||
<ClInclude Include="notifications.h" />
|
||||
<ClInclude Include="processApi.h" />
|
||||
<ClInclude Include="RcResource.h" />
|
||||
@@ -141,7 +145,9 @@
|
||||
<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="VideoCaptureDeviceList.h" />
|
||||
<ClInclude Include="window_helpers.h" />
|
||||
<ClInclude Include="icon_helpers.h" />
|
||||
<ClInclude Include="json.h" />
|
||||
@@ -170,7 +176,9 @@
|
||||
<ClCompile Include="dpi_aware.cpp" />
|
||||
<ClCompile Include="json.cpp" />
|
||||
<ClCompile Include="keyboard_layout.cpp" />
|
||||
<ClCompile Include="MicrophoneDevice.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,9 +193,11 @@
|
||||
<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" />
|
||||
<ClCompile Include="VideoCaptureDeviceList.cpp" />
|
||||
<ClCompile Include="windows_colors.cpp" />
|
||||
<ClCompile Include="window_helpers.cpp" />
|
||||
<ClCompile Include="winstore.cpp" />
|
||||
|
||||
@@ -141,6 +141,18 @@
|
||||
<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>
|
||||
<ClInclude Include="MicrophoneDevice.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="VideoCaptureDeviceList.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="d2d_svg.cpp">
|
||||
@@ -222,6 +234,18 @@
|
||||
<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>
|
||||
<ClCompile Include="MicrophoneDevice.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="VideoCaptureDeviceList.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
|
||||
@@ -1,4 +1,167 @@
|
||||
#include "pch.h"
|
||||
|
||||
#include "interop.h"
|
||||
#include <msclr\marshal.h>
|
||||
#include <msclr\marshal_cppstd.h>
|
||||
#include <functional>
|
||||
#include "..\keyboard_layout.h"
|
||||
#include "..\two_way_pipe_message_ipc.h"
|
||||
#include "..\common.h"
|
||||
#include "..\shared_constants.h"
|
||||
#include "..\os-detect.h"
|
||||
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 4793) // some functions must be compiled as native
|
||||
#include "..\MicrophoneDevice.h"
|
||||
#include "..\VideoCaptureDeviceList.h"
|
||||
#pragma warning(pop)
|
||||
|
||||
using namespace System;
|
||||
using namespace System::Runtime::InteropServices;
|
||||
using System::Collections::Generic::List;
|
||||
|
||||
//https://docs.microsoft.com/en-us/cpp/dotnet/how-to-wrap-native-class-for-use-by-csharp?view=vs-2019
|
||||
namespace interop
|
||||
{
|
||||
public
|
||||
ref class LayoutMapManaged
|
||||
{
|
||||
public:
|
||||
LayoutMapManaged() :
|
||||
_map(new LayoutMap) {}
|
||||
|
||||
~LayoutMapManaged()
|
||||
{
|
||||
delete _map;
|
||||
}
|
||||
|
||||
String ^ GetKeyName(DWORD key) {
|
||||
return gcnew String(_map->GetKeyName(key).c_str());
|
||||
}
|
||||
|
||||
void Updatelayout()
|
||||
{
|
||||
_map->UpdateLayout();
|
||||
}
|
||||
|
||||
protected:
|
||||
!LayoutMapManaged()
|
||||
{
|
||||
delete _map;
|
||||
}
|
||||
|
||||
private:
|
||||
LayoutMap* _map;
|
||||
};
|
||||
|
||||
public
|
||||
ref class TwoWayPipeMessageIPCManaged
|
||||
{
|
||||
public:
|
||||
delegate void ReadCallback(String ^ message);
|
||||
|
||||
TwoWayPipeMessageIPCManaged(String ^ inputPipeName, String ^ outputPipeName, ReadCallback ^ callback)
|
||||
{
|
||||
_wrapperCallback = gcnew InternalReadCallback(this, &TwoWayPipeMessageIPCManaged::ReadCallbackHelper);
|
||||
_callback = callback;
|
||||
|
||||
TwoWayPipeMessageIPC::callback_function cb = nullptr;
|
||||
if (callback != nullptr)
|
||||
{
|
||||
cb = (TwoWayPipeMessageIPC::callback_function)(void*)Marshal::GetFunctionPointerForDelegate(_wrapperCallback);
|
||||
}
|
||||
_pipe = new TwoWayPipeMessageIPC(
|
||||
msclr::interop::marshal_as<std::wstring>(inputPipeName),
|
||||
msclr::interop::marshal_as<std::wstring>(outputPipeName),
|
||||
cb);
|
||||
}
|
||||
|
||||
~TwoWayPipeMessageIPCManaged()
|
||||
{
|
||||
delete _pipe;
|
||||
}
|
||||
|
||||
void Send(String ^ msg)
|
||||
{
|
||||
_pipe->send(msclr::interop::marshal_as<std::wstring>(msg));
|
||||
}
|
||||
|
||||
void Start()
|
||||
{
|
||||
_pipe->start(nullptr);
|
||||
}
|
||||
|
||||
void End()
|
||||
{
|
||||
_pipe->end();
|
||||
}
|
||||
|
||||
protected:
|
||||
!TwoWayPipeMessageIPCManaged()
|
||||
{
|
||||
delete _pipe;
|
||||
}
|
||||
|
||||
private:
|
||||
delegate void InternalReadCallback(const std::wstring& msg);
|
||||
|
||||
TwoWayPipeMessageIPC* _pipe;
|
||||
ReadCallback ^ _callback;
|
||||
InternalReadCallback ^ _wrapperCallback;
|
||||
|
||||
void ReadCallbackHelper(const std::wstring& msg)
|
||||
{
|
||||
_callback(gcnew String(msg.c_str()));
|
||||
}
|
||||
};
|
||||
|
||||
public
|
||||
ref class CommonManaged
|
||||
{
|
||||
public:
|
||||
static String ^ GetProductVersion() {
|
||||
return gcnew String(get_product_version().c_str());
|
||||
}
|
||||
|
||||
static bool ShouldNewSettingsBeUsed()
|
||||
{
|
||||
return UseNewSettings();
|
||||
}
|
||||
|
||||
static List<String ^> ^ GetAllActiveMicrophoneDeviceNames() {
|
||||
auto names = gcnew List<String ^>();
|
||||
for (const auto& device : MicrophoneDevice::getAllActive())
|
||||
{
|
||||
names->Add(gcnew String(device.name().data()));
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
static List<String ^> ^
|
||||
GetAllVideoCaptureDeviceNames() {
|
||||
auto names = gcnew List<String ^>();
|
||||
VideoCaptureDeviceList vcdl;
|
||||
vcdl.EnumerateDevices();
|
||||
|
||||
for (UINT32 i = 0; i < vcdl.Count(); ++i)
|
||||
{
|
||||
auto name = gcnew String(vcdl.GetDeviceName(i).data());
|
||||
if (name != L"PowerToys VideoConference")
|
||||
{
|
||||
names->Add(name);
|
||||
}
|
||||
}
|
||||
return names;
|
||||
}
|
||||
};
|
||||
|
||||
public
|
||||
ref class Constants
|
||||
{
|
||||
public:
|
||||
literal int VK_WIN_BOTH = CommonSharedConstants::VK_WIN_BOTH;
|
||||
|
||||
static String ^ PowerLauncherSharedEvent() {
|
||||
return gcnew String(CommonSharedConstants::POWER_LAUNCHER_SHARED_EVENT);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,136 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <msclr\marshal.h>
|
||||
#include <msclr\marshal_cppstd.h>
|
||||
#include <functional>
|
||||
#include "..\keyboard_layout.h"
|
||||
#include "..\two_way_pipe_message_ipc.h"
|
||||
#include "..\common.h"
|
||||
#include "..\shared_constants.h"
|
||||
#include "..\os-detect.h"
|
||||
|
||||
using namespace System;
|
||||
using namespace System::Runtime::InteropServices;
|
||||
|
||||
//https://docs.microsoft.com/en-us/cpp/dotnet/how-to-wrap-native-class-for-use-by-csharp?view=vs-2019
|
||||
namespace interop
|
||||
{
|
||||
public
|
||||
ref class LayoutMapManaged
|
||||
{
|
||||
public:
|
||||
LayoutMapManaged() :
|
||||
_map(new LayoutMap) {}
|
||||
|
||||
~LayoutMapManaged()
|
||||
{
|
||||
delete _map;
|
||||
}
|
||||
|
||||
String ^ GetKeyName(DWORD key) {
|
||||
return gcnew String(_map->GetKeyName(key).c_str());
|
||||
}
|
||||
|
||||
void Updatelayout()
|
||||
{
|
||||
_map->UpdateLayout();
|
||||
}
|
||||
|
||||
protected:
|
||||
!LayoutMapManaged()
|
||||
{
|
||||
delete _map;
|
||||
}
|
||||
|
||||
private:
|
||||
LayoutMap* _map;
|
||||
};
|
||||
|
||||
public
|
||||
ref class TwoWayPipeMessageIPCManaged
|
||||
{
|
||||
public:
|
||||
delegate void ReadCallback(String ^ message);
|
||||
|
||||
TwoWayPipeMessageIPCManaged(String ^ inputPipeName, String ^ outputPipeName, ReadCallback ^ callback)
|
||||
{
|
||||
_wrapperCallback = gcnew InternalReadCallback(this, &TwoWayPipeMessageIPCManaged::ReadCallbackHelper);
|
||||
_callback = callback;
|
||||
|
||||
TwoWayPipeMessageIPC::callback_function cb = nullptr;
|
||||
if (callback != nullptr)
|
||||
{
|
||||
cb = (TwoWayPipeMessageIPC::callback_function)(void*)Marshal::GetFunctionPointerForDelegate(_wrapperCallback);
|
||||
}
|
||||
_pipe = new TwoWayPipeMessageIPC(
|
||||
msclr::interop::marshal_as<std::wstring>(inputPipeName),
|
||||
msclr::interop::marshal_as<std::wstring>(outputPipeName),
|
||||
cb);
|
||||
}
|
||||
|
||||
~TwoWayPipeMessageIPCManaged()
|
||||
{
|
||||
delete _pipe;
|
||||
}
|
||||
|
||||
void Send(String ^ msg)
|
||||
{
|
||||
_pipe->send(msclr::interop::marshal_as<std::wstring>(msg));
|
||||
}
|
||||
|
||||
void Start()
|
||||
{
|
||||
_pipe->start(nullptr);
|
||||
}
|
||||
|
||||
void End()
|
||||
{
|
||||
_pipe->end();
|
||||
}
|
||||
|
||||
protected:
|
||||
!TwoWayPipeMessageIPCManaged()
|
||||
{
|
||||
delete _pipe;
|
||||
}
|
||||
|
||||
private:
|
||||
delegate void InternalReadCallback(const std::wstring& msg);
|
||||
|
||||
TwoWayPipeMessageIPC* _pipe;
|
||||
ReadCallback ^ _callback;
|
||||
InternalReadCallback ^ _wrapperCallback;
|
||||
|
||||
void ReadCallbackHelper(const std::wstring& msg)
|
||||
{
|
||||
_callback(gcnew String(msg.c_str()));
|
||||
}
|
||||
};
|
||||
|
||||
public
|
||||
ref class CommonManaged
|
||||
{
|
||||
public:
|
||||
static String^ GetProductVersion()
|
||||
{
|
||||
return gcnew String(get_product_version().c_str());
|
||||
}
|
||||
|
||||
static bool ShouldNewSettingsBeUsed()
|
||||
{
|
||||
return UseNewSettings();
|
||||
}
|
||||
};
|
||||
|
||||
public
|
||||
ref class Constants
|
||||
{
|
||||
public:
|
||||
literal int VK_WIN_BOTH = CommonSharedConstants::VK_WIN_BOTH;
|
||||
|
||||
static String^ PowerLauncherSharedEvent()
|
||||
{
|
||||
return gcnew String(CommonSharedConstants::POWER_LAUNCHER_SHARED_EVENT);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -23,6 +23,7 @@
|
||||
<Keyword>ManagedCProj</Keyword>
|
||||
<RootNamespace>interop</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>10.0.17134.0</WindowsTargetPlatformVersion>
|
||||
<ProjectName>PowerToysInterop</ProjectName>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
@@ -73,7 +74,7 @@
|
||||
<LanguageStandard>stdcpp17</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<AdditionalDependencies>WindowsApp.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalDependencies>WindowsApp.lib;Mf.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<DelayLoadDLLs>
|
||||
</DelayLoadDLLs>
|
||||
</Link>
|
||||
@@ -87,7 +88,7 @@
|
||||
<LanguageStandard>stdcpp17</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<AdditionalDependencies>WindowsApp.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalDependencies>WindowsApp.lib;Mf.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<DelayLoadDLLs>
|
||||
</DelayLoadDLLs>
|
||||
</Link>
|
||||
@@ -127,7 +128,6 @@
|
||||
</Target>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="HotkeyManager.h" />
|
||||
<ClInclude Include="interop.h" />
|
||||
<ClInclude Include="KeyboardHook.h" />
|
||||
<ClInclude Include="pch.h" />
|
||||
<ClInclude Include="resource.h" />
|
||||
@@ -149,6 +149,9 @@
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\common-md-flag\common-md-flag.vcxproj">
|
||||
<Project>{985b3f2f-ceed-4c0a-a249-69257e719145}</Project>
|
||||
@@ -156,5 +159,12 @@
|
||||
</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>
|
||||
@@ -15,9 +15,6 @@
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="interop.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="pch.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
|
||||
4
src/common/interop/packages.config
Normal 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>
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
#include <string_view>
|
||||
#include <string>
|
||||
|
||||
std::wstring ObtainStableGlobalNameForKernelObject(const std::wstring_view name, const bool restricted);
|
||||
@@ -1,7 +1,12 @@
|
||||
#define NOMINMAX
|
||||
#include <Windows.h>
|
||||
#include <Unknwn.h>
|
||||
|
||||
#include <winrt/base.h>
|
||||
#include <winrt/Windows.Foundation.Collections.h>
|
||||
#include <Windows.h>
|
||||
#include <wil/resource.h>
|
||||
#include <wil/com.h>
|
||||
|
||||
#include <dxgi1_3.h>
|
||||
#include <d3d11_2.h>
|
||||
#include <d2d1_3.h>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -203,7 +203,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();
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -50,7 +50,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;
|
||||
}
|
||||
|
||||
@@ -70,14 +70,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;
|
||||
|
||||
@@ -99,7 +99,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));
|
||||
}
|
||||
|
||||
@@ -111,7 +111,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));
|
||||
}
|
||||
|
||||
@@ -139,7 +139,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;
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -100,55 +101,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;
|
||||
@@ -237,19 +290,6 @@ namespace updating
|
||||
}
|
||||
}
|
||||
|
||||
std::future<std::wstring> check_new_version_available()
|
||||
{
|
||||
const auto new_version = co_await get_new_github_version_info_async();
|
||||
if (!new_version)
|
||||
{
|
||||
updating::notifications::show_unavailable();
|
||||
co_return VersionHelper{ VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION }.toWstring();
|
||||
}
|
||||
|
||||
updating::notifications::show_available(new_version.value());
|
||||
co_return new_version->version_string;
|
||||
}
|
||||
|
||||
std::future<std::wstring> download_update()
|
||||
{
|
||||
const auto new_version = co_await get_new_github_version_info_async();
|
||||
|
||||
@@ -20,17 +20,17 @@ 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();
|
||||
|
||||
std::future<std::wstring> check_new_version_available();
|
||||
std::future<std::wstring> download_update();
|
||||
|
||||
// non-localized
|
||||
|
||||
20
src/common/user.cpp
Normal 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
@@ -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);
|
||||
@@ -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
|
||||
|
||||
@@ -48,4 +48,10 @@
|
||||
<ProjectReference Include="..\..\common\ManagedTelemetry\Telemetry\Telemetry.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="System.Windows.Forms">
|
||||
<HintPath>C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.7.2\System.Windows.Forms.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
VideoConference = settings;
|
||||
}
|
||||
|
||||
public string ToJsonString()
|
||||
{
|
||||
return JsonSerializer.Serialize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.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,
|
||||
});
|
||||
|
||||
this.HideToolbarWhenUnmuted = new BoolProperty(true);
|
||||
}
|
||||
|
||||
[JsonPropertyName("mute_camera_and_microphone_hotkey")]
|
||||
public KeyboardKeysProperty MuteCameraAndMicrophoneHotkey { get; set; }
|
||||
|
||||
[JsonPropertyName("mute_microphone_hotkey")]
|
||||
public KeyboardKeysProperty MuteMicrophoneHotkey { get; set; }
|
||||
|
||||
[JsonPropertyName("mute_camera_hotkey")]
|
||||
public KeyboardKeysProperty MuteCameraHotkey { get; set; }
|
||||
|
||||
[JsonPropertyName("selected_camera")]
|
||||
public StringProperty SelectedCamera { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("selected_mic")]
|
||||
public StringProperty SelectedMicrophone { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("toolbar_position")]
|
||||
public StringProperty ToolbarPosition { get; set; } = "Top right corner";
|
||||
|
||||
[JsonPropertyName("toolbar_monitor")]
|
||||
public StringProperty ToolbarMonitor { get; set; } = "Main monitor";
|
||||
|
||||
[JsonPropertyName("camera_overlay_image_path")]
|
||||
public StringProperty CameraOverlayImagePath { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("theme")]
|
||||
public StringProperty Theme { get; set; }
|
||||
|
||||
[JsonPropertyName("hide_toolbar_when_unmuted")]
|
||||
public BoolProperty HideToolbarWhenUnmuted { get; set; }
|
||||
|
||||
// converts the current to a json string.
|
||||
public string ToJsonString()
|
||||
{
|
||||
return JsonSerializer.Serialize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Microsoft.PowerToys.Settings.UI.Lib.Interface;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.Lib
|
||||
{
|
||||
public class VideoConferenceSettings : BasePTModuleSettings, ISettingsConfig
|
||||
{
|
||||
public VideoConferenceSettings()
|
||||
{
|
||||
Version = "1";
|
||||
Name = "Video Conference";
|
||||
Properties = new VideoConferenceConfigProperties();
|
||||
}
|
||||
|
||||
[JsonPropertyName("properties")]
|
||||
public VideoConferenceConfigProperties Properties { get; set; }
|
||||
|
||||
public string GetModuleName()
|
||||
{
|
||||
return Name;
|
||||
}
|
||||
|
||||
bool ISettingsConfig.UpgradeSettingsConfiguration()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,414 @@
|
||||
// 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 System.Windows.Forms;
|
||||
using Microsoft.PowerToys.Settings.UI.Lib;
|
||||
using Microsoft.PowerToys.Settings.UI.Lib.Helpers;
|
||||
using Microsoft.PowerToys.Settings.UI.Lib.Interface;
|
||||
using Microsoft.PowerToys.Settings.UI.Lib.ViewModels.Commands;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
{
|
||||
public class VideoConferenceViewModel : Observable
|
||||
{
|
||||
private readonly ISettingsUtils _settingsUtils;
|
||||
|
||||
private VideoConferenceSettings Settings { get; set; }
|
||||
|
||||
private GeneralSettings GeneralSettingsConfig { get; set; }
|
||||
|
||||
private const string ModuleName = "Video Conference";
|
||||
|
||||
private Func<string, int> SendConfigMSG { get; }
|
||||
|
||||
private string _settingsConfigFileFolder = string.Empty;
|
||||
|
||||
public VideoConferenceViewModel(ISettingsUtils settingsUtils, ISettingsRepository<GeneralSettings> settingsRepository, Func<string, int> ipcMSGCallBackFunc, string configFileSubfolder = "")
|
||||
{
|
||||
GeneralSettingsConfig = settingsRepository.SettingsConfig;
|
||||
|
||||
_settingsUtils = settingsUtils ?? throw new ArgumentNullException(nameof(settingsUtils));
|
||||
|
||||
SendConfigMSG = ipcMSGCallBackFunc;
|
||||
|
||||
_settingsConfigFileFolder = configFileSubfolder;
|
||||
|
||||
try
|
||||
{
|
||||
Settings = _settingsUtils.GetSettings<VideoConferenceSettings>(GetSettingsSubPath());
|
||||
}
|
||||
catch
|
||||
{
|
||||
Settings = new VideoConferenceSettings();
|
||||
_settingsUtils.SaveSettings(Settings.ToJsonString(), GetSettingsSubPath());
|
||||
}
|
||||
|
||||
CameraNames = interop.CommonManaged.GetAllVideoCaptureDeviceNames();
|
||||
MicrophoneNames = interop.CommonManaged.GetAllActiveMicrophoneDeviceNames();
|
||||
MicrophoneNames.Insert(0, "[All]");
|
||||
|
||||
var shouldSaveSettings = false;
|
||||
|
||||
if (Settings.Properties.SelectedCamera.Value == string.Empty && CameraNames.Count != 0)
|
||||
{
|
||||
_selectedCameraIndex = 0;
|
||||
Settings.Properties.SelectedCamera.Value = CameraNames[0];
|
||||
shouldSaveSettings = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
_selectedCameraIndex = CameraNames.FindIndex(name => name == Settings.Properties.SelectedCamera.Value);
|
||||
}
|
||||
|
||||
if (Settings.Properties.SelectedMicrophone.Value == string.Empty)
|
||||
{
|
||||
_selectedMicrophoneIndex = 0;
|
||||
Settings.Properties.SelectedMicrophone.Value = MicrophoneNames[0];
|
||||
shouldSaveSettings = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
_selectedMicrophoneIndex = MicrophoneNames.FindIndex(name => name == Settings.Properties.SelectedMicrophone.Value);
|
||||
}
|
||||
|
||||
_isEnabled = GeneralSettingsConfig.Enabled.VideoConference;
|
||||
_cameraAndMicrophoneMuteHotkey = Settings.Properties.MuteCameraAndMicrophoneHotkey.Value;
|
||||
_mirophoneMuteHotkey = Settings.Properties.MuteMicrophoneHotkey.Value;
|
||||
_cameraMuteHotkey = Settings.Properties.MuteCameraHotkey.Value;
|
||||
CameraImageOverlayPath = Settings.Properties.CameraOverlayImagePath.Value;
|
||||
SelectOverlayImage = new ButtonClickCommand(SelectOverlayImageAction);
|
||||
ClearOverlayImage = new ButtonClickCommand(ClearOverlayImageAction);
|
||||
|
||||
_hideToolbarWhenUnmuted = Settings.Properties.HideToolbarWhenUnmuted.Value;
|
||||
|
||||
switch (Settings.Properties.ToolbarPosition.Value)
|
||||
{
|
||||
case "Top left corner":
|
||||
_toolbarPositionIndex = 0;
|
||||
break;
|
||||
case "Top center":
|
||||
_toolbarPositionIndex = 1;
|
||||
break;
|
||||
case "Top right corner":
|
||||
_toolbarPositionIndex = 2;
|
||||
break;
|
||||
case "Bottom left corner":
|
||||
_toolbarPositionIndex = 3;
|
||||
break;
|
||||
case "Bottom center":
|
||||
_toolbarPositionIndex = 4;
|
||||
break;
|
||||
case "Bottom right corner":
|
||||
_toolbarPositionIndex = 5;
|
||||
break;
|
||||
}
|
||||
|
||||
switch (Settings.Properties.ToolbarMonitor.Value)
|
||||
{
|
||||
case "Main monitor":
|
||||
_toolbarMonitorIndex = 0;
|
||||
break;
|
||||
|
||||
case "All monitors":
|
||||
_toolbarMonitorIndex = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
if (shouldSaveSettings)
|
||||
{
|
||||
_settingsUtils.SaveSettings(Settings.ToJsonString(), ModuleName);
|
||||
}
|
||||
}
|
||||
|
||||
private bool _isEnabled = false;
|
||||
private int _toolbarPositionIndex;
|
||||
private int _toolbarMonitorIndex;
|
||||
private HotkeySettings _cameraAndMicrophoneMuteHotkey;
|
||||
private HotkeySettings _mirophoneMuteHotkey;
|
||||
private HotkeySettings _cameraMuteHotkey;
|
||||
private int _selectedCameraIndex = -1;
|
||||
private int _selectedMicrophoneIndex = 0;
|
||||
private bool _hideToolbarWhenUnmuted;
|
||||
|
||||
public List<string> CameraNames { get; }
|
||||
|
||||
public List<string> MicrophoneNames { get; }
|
||||
|
||||
public string CameraImageOverlayPath { get; set; }
|
||||
|
||||
public ButtonClickCommand SelectOverlayImage { get; set; }
|
||||
|
||||
public ButtonClickCommand ClearOverlayImage { get; set; }
|
||||
|
||||
private void ClearOverlayImageAction()
|
||||
{
|
||||
CameraImageOverlayPath = string.Empty;
|
||||
Settings.Properties.CameraOverlayImagePath = string.Empty;
|
||||
RaisePropertyChanged("CameraImageOverlayPath");
|
||||
}
|
||||
|
||||
private void SelectOverlayImageAction()
|
||||
{
|
||||
try
|
||||
{
|
||||
string pickedImage = null;
|
||||
using (OpenFileDialog openFileDialog = new OpenFileDialog())
|
||||
{
|
||||
openFileDialog.Filter = "Image Files (*.jpeg;*.jpg;*.png)|*.jpeg;*.jpg;*.png";
|
||||
openFileDialog.RestoreDirectory = true;
|
||||
if (openFileDialog.ShowDialog() == DialogResult.OK)
|
||||
{
|
||||
pickedImage = openFileDialog.FileName;
|
||||
}
|
||||
}
|
||||
|
||||
if (pickedImage != null)
|
||||
{
|
||||
CameraImageOverlayPath = pickedImage;
|
||||
Settings.Properties.CameraOverlayImagePath = pickedImage;
|
||||
RaisePropertyChanged("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 int SelectedMicrophoneIndex
|
||||
{
|
||||
get
|
||||
{
|
||||
return _selectedMicrophoneIndex;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (_selectedMicrophoneIndex != value)
|
||||
{
|
||||
_selectedMicrophoneIndex = value;
|
||||
if (_selectedMicrophoneIndex >= 0 && _selectedMicrophoneIndex < MicrophoneNames.Count())
|
||||
{
|
||||
Settings.Properties.SelectedMicrophone.Value = MicrophoneNames[_selectedMicrophoneIndex];
|
||||
RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsEnabled
|
||||
{
|
||||
get
|
||||
{
|
||||
return _isEnabled;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (value != _isEnabled)
|
||||
{
|
||||
_isEnabled = value;
|
||||
GeneralSettingsConfig.Enabled.VideoConference = value;
|
||||
OutGoingGeneralSettings snd = new OutGoingGeneralSettings(GeneralSettingsConfig);
|
||||
|
||||
SendConfigMSG(snd.ToString());
|
||||
OnPropertyChanged("IsEnabled");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public HotkeySettings CameraAndMicrophoneMuteHotkey
|
||||
{
|
||||
get
|
||||
{
|
||||
return _cameraAndMicrophoneMuteHotkey;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (value != _cameraAndMicrophoneMuteHotkey)
|
||||
{
|
||||
_cameraAndMicrophoneMuteHotkey = value;
|
||||
Settings.Properties.MuteCameraAndMicrophoneHotkey.Value = value;
|
||||
RaisePropertyChanged("CameraAndMicrophoneMuteHotkey");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public HotkeySettings MicrophoneMuteHotkey
|
||||
{
|
||||
get
|
||||
{
|
||||
return _mirophoneMuteHotkey;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (value != _mirophoneMuteHotkey)
|
||||
{
|
||||
_mirophoneMuteHotkey = value;
|
||||
Settings.Properties.MuteMicrophoneHotkey.Value = value;
|
||||
RaisePropertyChanged("MicrophoneMuteHotkey");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public HotkeySettings CameraMuteHotkey
|
||||
{
|
||||
get
|
||||
{
|
||||
return _cameraMuteHotkey;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (value != _cameraMuteHotkey)
|
||||
{
|
||||
_cameraMuteHotkey = value;
|
||||
Settings.Properties.MuteCameraHotkey.Value = value;
|
||||
RaisePropertyChanged("CameraMuteHotkey");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int ToolbarPostionIndex
|
||||
{
|
||||
get
|
||||
{
|
||||
return _toolbarPositionIndex;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (_toolbarPositionIndex != value)
|
||||
{
|
||||
_toolbarPositionIndex = value;
|
||||
switch (_toolbarPositionIndex)
|
||||
{
|
||||
case 0:
|
||||
Settings.Properties.ToolbarPosition.Value = "Top left corner";
|
||||
break;
|
||||
|
||||
case 1:
|
||||
Settings.Properties.ToolbarPosition.Value = "Top center";
|
||||
break;
|
||||
|
||||
case 2:
|
||||
Settings.Properties.ToolbarPosition.Value = "Top right corner";
|
||||
break;
|
||||
|
||||
case 3:
|
||||
Settings.Properties.ToolbarPosition.Value = "Bottom left corner";
|
||||
break;
|
||||
|
||||
case 4:
|
||||
Settings.Properties.ToolbarPosition.Value = "Bottom center";
|
||||
break;
|
||||
|
||||
case 5:
|
||||
Settings.Properties.ToolbarPosition.Value = "Bottom right corner";
|
||||
break;
|
||||
}
|
||||
|
||||
RaisePropertyChanged("ToolbarPostionIndex");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int ToolbarMonitorIndex
|
||||
{
|
||||
get
|
||||
{
|
||||
return _toolbarMonitorIndex;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (_toolbarMonitorIndex != value)
|
||||
{
|
||||
_toolbarMonitorIndex = value;
|
||||
switch (_toolbarMonitorIndex)
|
||||
{
|
||||
case 0:
|
||||
Settings.Properties.ToolbarMonitor.Value = "Main monitor";
|
||||
break;
|
||||
|
||||
case 1:
|
||||
Settings.Properties.ToolbarMonitor.Value = "All monitors";
|
||||
break;
|
||||
}
|
||||
|
||||
RaisePropertyChanged("ToolbarMonitorIndex");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool HideToolbarWhenUnmuted
|
||||
{
|
||||
get
|
||||
{
|
||||
return _hideToolbarWhenUnmuted;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (value != _hideToolbarWhenUnmuted)
|
||||
{
|
||||
_hideToolbarWhenUnmuted = value;
|
||||
Settings.Properties.HideToolbarWhenUnmuted.Value = value;
|
||||
RaisePropertyChanged("HideToolbarWhenUnmuted");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 68 KiB |
@@ -141,6 +141,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>
|
||||
@@ -159,6 +162,7 @@
|
||||
<Content Include="Assets\Modules\PowerRename.png" />
|
||||
<Content Include="Assets\Modules\PT.png" />
|
||||
<Content Include="Assets\Modules\ShortcutGuide.png" />
|
||||
<Content Include="Assets\Modules\VideoConference.png" />
|
||||
<Content Include="Assets\SplashScreen.png" />
|
||||
<Content Include="Assets\Square150x150Logo.scale-200.png" />
|
||||
<Content Include="Assets\Square44x44Logo.scale-200.png" />
|
||||
@@ -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.
|
||||
|
||||
@@ -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 Mute</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>
|
||||
@@ -492,6 +496,70 @@
|
||||
<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>Video Conference Mute is a quick and easy way to do an global "mute" of both your microphone and webcam.
|
||||
Disabling this module or closing PowerToys will unmute the microphone and camera.</value>
|
||||
</data>
|
||||
<data name="VideoConference_CameraAndMicrophoneMuteHotkeyControl_Header.Header" xml:space="preserve">
|
||||
<value>Mute camera and microphone</value>
|
||||
</data>
|
||||
<data name="VideoConference_MicrophoneMuteHotkeyControl_Header.Header" xml:space="preserve">
|
||||
<value>Mute microphone</value>
|
||||
</data>
|
||||
<data name="VideoConference_CameraMuteHotkeyControl_Header.Header" xml:space="preserve">
|
||||
<value>Mute camera</value>
|
||||
</data>
|
||||
<data name="VideoConference_SelectedCamera.Header" xml:space="preserve">
|
||||
<value>Selected camera</value>
|
||||
</data>
|
||||
<data name="VideoConference_SelectedMicrophone.Header" xml:space="preserve">
|
||||
<value>Selected microphone</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>
|
||||
@@ -675,6 +743,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>
|
||||
@@ -747,6 +818,18 @@
|
||||
<data name="FancyZones_MoveWindowsBasedOnPositionCheckBoxControl.Content" xml:space="preserve">
|
||||
<value>Move windows based on their position</value>
|
||||
</data>
|
||||
<data name="VideoConference_Camera.Text" xml:space="preserve">
|
||||
<value>Camera</value>
|
||||
</data>
|
||||
<data name="VideoConference_Microphone.Text" xml:space="preserve">
|
||||
<value>Microphone</value>
|
||||
</data>
|
||||
<data name="VideoConference_Toolbar.Text" xml:space="preserve">
|
||||
<value>Toolbar</value>
|
||||
</data>
|
||||
<data name="VideoConference_Shortcuts.Text" xml:space="preserve">
|
||||
<value>Shortcuts</value>
|
||||
</data>
|
||||
<data name="GeneralSettings_NewVersionIsAvailable" xml:space="preserve">
|
||||
<value>New update available</value>
|
||||
</data>
|
||||
@@ -759,4 +842,13 @@
|
||||
<data name="FileExplorerPreview_PreviewPane_GroupSettings.Text" xml:space="preserve">
|
||||
<value>Preview Pane</value>
|
||||
</data>
|
||||
<data name="VideoConference_CameraOverlayImageAlt.AutomationProperties.Name" xml:space="preserve">
|
||||
<value>Camera overlay image preview</value>
|
||||
</data>
|
||||
<data name="VideoConference_CameraOverlayImageBrowse.Content" xml:space="preserve">
|
||||
<value>Browse</value>
|
||||
</data>
|
||||
<data name="VideoConference_CameraOverlayImageClear.Content" xml:space="preserve">
|
||||
<value>Clear</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -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; }
|
||||
|
||||
@@ -88,6 +88,13 @@
|
||||
<FontIcon Glyph=""/>
|
||||
</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=""/>
|
||||
</winui:NavigationViewItem.Icon>
|
||||
</winui:NavigationViewItem>
|
||||
|
||||
</winui:NavigationView.MenuItems>
|
||||
<i:Interaction.Behaviors>
|
||||
<behaviors:NavigationViewHeaderBehavior
|
||||
@@ -116,4 +123,4 @@
|
||||
</ScrollViewer>
|
||||
</winui:NavigationView>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
</UserControl>
|
||||
|
||||
@@ -0,0 +1,207 @@
|
||||
<Page
|
||||
x:Class="Microsoft.PowerToys.Settings.UI.Views.VideoConferencePage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:CustomControls="using:Microsoft.PowerToys.Settings.UI.Controls"
|
||||
mc:Ignorable="d"
|
||||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||
|
||||
<Grid RowSpacing="{StaticResource DefaultRowSpacing}">
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup x:Name="LayoutVisualStates">
|
||||
<VisualState x:Name="WideLayout">
|
||||
<VisualState.StateTriggers>
|
||||
<AdaptiveTrigger MinWindowWidth="{StaticResource WideLayoutMinWidth}" />
|
||||
</VisualState.StateTriggers>
|
||||
</VisualState>
|
||||
<VisualState x:Name="SmallLayout">
|
||||
<VisualState.StateTriggers>
|
||||
<AdaptiveTrigger MinWindowWidth="{StaticResource SmallLayoutMinWidth}" />
|
||||
<AdaptiveTrigger MinWindowWidth="0" />
|
||||
</VisualState.StateTriggers>
|
||||
<VisualState.Setters>
|
||||
<Setter Target="SidePanel.(Grid.Column)" Value="0"/>
|
||||
<Setter Target="SidePanel.Width" Value="Auto"/>
|
||||
<Setter Target="VideoConferenceView.(Grid.Row)" Value="1" />
|
||||
<Setter Target="LinksPanel.(RelativePanel.RightOf)" Value="AboutImage"/>
|
||||
<Setter Target="LinksPanel.(RelativePanel.AlignTopWith)" Value="AboutImage"/>
|
||||
<Setter Target="AboutImage.Margin" Value="0,12,12,0"/>
|
||||
<Setter Target="AboutTitle.Visibility" Value="Collapsed" />
|
||||
</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">
|
||||
|
||||
<ToggleSwitch x:Uid="VideoConference_Enable"
|
||||
IsOn="{ Binding Mode=TwoWay, Path=IsEnabled}"
|
||||
/>
|
||||
|
||||
<TextBlock x:Uid="VideoConference_Shortcuts"
|
||||
Style="{StaticResource SettingsGroupTitleStyle}"
|
||||
Foreground="{x:Bind Mode=OneWay, Path=ViewModel.IsEnabled, Converter={StaticResource ModuleEnabledToForegroundConverter}}"/>
|
||||
|
||||
<CustomControls:HotkeySettingsControl
|
||||
x:Uid="VideoConference_CameraAndMicrophoneMuteHotkeyControl_Header"
|
||||
Width="240"
|
||||
HorizontalAlignment="Left"
|
||||
Margin="{StaticResource SmallTopMargin}"
|
||||
Keys="Win, Ctrl, Alt, Shift"
|
||||
HotkeySettings="{x:Bind Path=ViewModel.CameraAndMicrophoneMuteHotkey, Mode=TwoWay}"
|
||||
IsEnabled="{ Binding Mode=TwoWay, Path=IsEnabled}"
|
||||
/>
|
||||
|
||||
<CustomControls:HotkeySettingsControl
|
||||
x:Uid="VideoConference_MicrophoneMuteHotkeyControl_Header"
|
||||
Width="240"
|
||||
HorizontalAlignment="Left"
|
||||
Margin="{StaticResource SmallTopMargin}"
|
||||
Keys="Win, Ctrl, Alt, Shift"
|
||||
HotkeySettings="{x:Bind Path=ViewModel.MicrophoneMuteHotkey, Mode=TwoWay}"
|
||||
IsEnabled="{ Binding Mode=TwoWay, Path=IsEnabled}"
|
||||
/>
|
||||
|
||||
<CustomControls:HotkeySettingsControl
|
||||
x:Uid="VideoConference_CameraMuteHotkeyControl_Header"
|
||||
Width="240"
|
||||
HorizontalAlignment="Left"
|
||||
Margin="{StaticResource SmallTopMargin}"
|
||||
Keys="Win, Ctrl, Alt, Shift"
|
||||
HotkeySettings="{x:Bind Path=ViewModel.CameraMuteHotkey, Mode=TwoWay}"
|
||||
IsEnabled="{ Binding Mode=TwoWay, Path=IsEnabled}"
|
||||
/>
|
||||
<TextBlock x:Uid="VideoConference_Microphone"
|
||||
Style="{StaticResource SettingsGroupTitleStyle}"
|
||||
Foreground="{x:Bind Mode=OneWay, Path=ViewModel.IsEnabled, Converter={StaticResource ModuleEnabledToForegroundConverter}}"/>
|
||||
|
||||
<ComboBox x:Uid="VideoConference_SelectedMicrophone"
|
||||
Width="240"
|
||||
HorizontalAlignment="Left"
|
||||
Margin="{StaticResource SmallTopMargin}"
|
||||
SelectedIndex="{Binding Path=SelectedMicrophoneIndex, Mode=TwoWay}"
|
||||
ItemsSource="{Binding MicrophoneNames, Mode=OneTime}"
|
||||
IsEnabled="{ Binding Mode=TwoWay, Path=IsEnabled}"/>
|
||||
|
||||
<TextBlock x:Uid="VideoConference_Camera"
|
||||
Style="{StaticResource SettingsGroupTitleStyle}"
|
||||
Foreground="{x:Bind Mode=OneWay, Path=ViewModel.IsEnabled, Converter={StaticResource ModuleEnabledToForegroundConverter}}"/>
|
||||
|
||||
<ComboBox x:Uid="VideoConference_SelectedCamera"
|
||||
Width="240"
|
||||
HorizontalAlignment="Left"
|
||||
Margin="{StaticResource SmallTopMargin}"
|
||||
SelectedIndex="{Binding Path=SelectedCameraIndex, Mode=TwoWay}"
|
||||
ItemsSource="{Binding CameraNames, Mode=OneTime}"
|
||||
IsEnabled="{ Binding Mode=TwoWay, Path=IsEnabled}"/>
|
||||
|
||||
<TextBlock x:Uid="VideoConference_CameraOverlayImagePathHeader"
|
||||
Margin="{StaticResource SmallTopMargin}"/>
|
||||
|
||||
<Border CornerRadius="4"
|
||||
HorizontalAlignment="Left"
|
||||
Margin="{StaticResource XXSmallTopMargin}">
|
||||
<Image Width="240"
|
||||
x:Uid="VideoConference_CameraOverlayImageAlt"
|
||||
ToolTipService.ToolTip="{Binding Mode=OneWay, Path=CameraImageOverlayPath}"
|
||||
Source="{Binding Mode=OneWay, Path=CameraImageOverlayPath}"/>
|
||||
</Border>
|
||||
|
||||
<StackPanel Orientation="Horizontal"
|
||||
Padding="0"
|
||||
Spacing="8"
|
||||
Margin="{StaticResource SmallTopMargin}">
|
||||
<Button Height="32"
|
||||
IsEnabled="{ Binding Mode=TwoWay, Path=IsEnabled}"
|
||||
x:Uid="VideoConference_CameraOverlayImageBrowse"
|
||||
Command="{Binding Mode=OneWay, Path=SelectOverlayImage}"
|
||||
HorizontalContentAlignment="Left"
|
||||
HorizontalAlignment="Left" />
|
||||
|
||||
<Button IsEnabled="{Binding Mode=TwoWay, Path=IsEnabled}"
|
||||
Height="32"
|
||||
x:Uid="VideoConference_CameraOverlayImageClear"
|
||||
Command="{Binding Mode=OneWay, Path=ClearOverlayImage}"
|
||||
HorizontalAlignment="Left" />
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock x:Uid="VideoConference_Toolbar"
|
||||
Style="{StaticResource SettingsGroupTitleStyle}"
|
||||
Foreground="{x:Bind Mode=OneWay, Path=ViewModel.IsEnabled, Converter={StaticResource ModuleEnabledToForegroundConverter}}"/>
|
||||
|
||||
<ComboBox x:Uid="VideoConference_ToolbarPosition"
|
||||
MinWidth="240"
|
||||
Margin="{StaticResource SmallTopMargin}"
|
||||
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 SmallTopMargin}"
|
||||
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>
|
||||
<RelativePanel x:Name="SidePanel"
|
||||
HorizontalAlignment="Left"
|
||||
Width="{StaticResource SidePanelWidth}"
|
||||
Grid.Column="1">
|
||||
<StackPanel x:Name="DescriptionPanel">
|
||||
<TextBlock x:Uid="About_VideoConference"
|
||||
x:Name="AboutTitle"
|
||||
Grid.ColumnSpan="2"
|
||||
Style="{StaticResource SettingsGroupTitleStyle}"
|
||||
Margin="{StaticResource XSmallBottomMargin}"/>
|
||||
<TextBlock x:Uid="VideoConference_Description"
|
||||
TextWrapping="Wrap"
|
||||
Grid.Row="1" />
|
||||
</StackPanel>
|
||||
|
||||
<Border x:Name="AboutImage"
|
||||
CornerRadius="4"
|
||||
Grid.Row="2"
|
||||
MaxWidth="240"
|
||||
HorizontalAlignment="Left"
|
||||
Margin="{StaticResource SmallTopBottomMargin}"
|
||||
RelativePanel.Below="DescriptionPanel">
|
||||
<Image Source="ms-appx:///Assets/Modules/VideoConference.png" />
|
||||
</Border>
|
||||
|
||||
<StackPanel x:Name="LinksPanel"
|
||||
Margin="0,1,0,0"
|
||||
RelativePanel.Below="AboutImage"
|
||||
Orientation="Vertical" >
|
||||
<HyperlinkButton NavigateUri="https://aka.ms/PowerToysOverview_VideoConference">
|
||||
<TextBlock x:Uid="Module_overview" />
|
||||
</HyperlinkButton>
|
||||
<HyperlinkButton NavigateUri="https://aka.ms/powerToysGiveFeedback">
|
||||
<TextBlock x:Uid="Give_Feedback" />
|
||||
</HyperlinkButton>
|
||||
</StackPanel>
|
||||
</RelativePanel>
|
||||
</Grid>
|
||||
</Page>
|
||||
@@ -0,0 +1,24 @@
|
||||
// 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.Lib;
|
||||
using Microsoft.PowerToys.Settings.UI.Lib.Utilities;
|
||||
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()
|
||||
{
|
||||
var settingsUtils = new SettingsUtils(new SystemIOProvider());
|
||||
ViewModel = new VideoConferenceViewModel(settingsUtils, SettingsRepository<GeneralSettings>.GetInstance(settingsUtils), ShellPage.SendDefaultIPCMessage);
|
||||
DataContext = ViewModel;
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
15
src/hasWDK.props
Normal 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>
|
||||
@@ -171,7 +171,7 @@ std::optional<FancyZonesDataTypes::CustomZoneSetData> FancyZonesData::FindCustom
|
||||
return it != end(customZoneSetsMap) ? std::optional{ it->second } : std::nullopt;
|
||||
}
|
||||
|
||||
void FancyZonesData::AddDevice(const std::wstring& deviceId)
|
||||
bool FancyZonesData::AddDevice(const std::wstring& deviceId)
|
||||
{
|
||||
using namespace FancyZonesDataTypes;
|
||||
|
||||
@@ -192,7 +192,11 @@ void FancyZonesData::AddDevice(const std::wstring& deviceId)
|
||||
{
|
||||
deviceInfoMap[deviceId] = DeviceInfoData{ ZoneSetData{ NonLocalizable::NullStr, ZoneSetLayoutType::Blank } };
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void FancyZonesData::CloneDeviceInfo(const std::wstring& source, const std::wstring& destination)
|
||||
@@ -209,7 +213,6 @@ void FancyZonesData::CloneDeviceInfo(const std::wstring& source, const std::wstr
|
||||
return;
|
||||
}
|
||||
|
||||
// Clone information from source device if destination device is uninitialized (Blank).
|
||||
deviceInfoMap[destination] = deviceInfoMap[source];
|
||||
}
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ public:
|
||||
return appZoneHistoryMap;
|
||||
}
|
||||
|
||||
void AddDevice(const std::wstring& deviceId);
|
||||
bool AddDevice(const std::wstring& deviceId);
|
||||
void CloneDeviceInfo(const std::wstring& source, const std::wstring& destination);
|
||||
void UpdatePrimaryDesktopData(const std::wstring& desktopId);
|
||||
void RemoveDeletedDesktops(const std::vector<std::wstring>& activeDesktops);
|
||||
|
||||
@@ -335,10 +335,10 @@ void WindowMoveHandlerPrivate::MoveSizeEnd(HWND window, POINT const& ptScreen, c
|
||||
auto zoneWindow = std::move(m_zoneWindowMoveSize);
|
||||
ResetWindowTransparency();
|
||||
|
||||
bool hasNoVisibleOwnoer = FancyZonesUtils::HasNoVisibleOwner(window);
|
||||
bool hasNoVisibleOwner = FancyZonesUtils::HasNoVisibleOwner(window);
|
||||
bool isStandardWindow = FancyZonesUtils::IsStandardWindow(window);
|
||||
|
||||
if ((isStandardWindow == false && hasNoVisibleOwnoer == false &&
|
||||
if ((isStandardWindow == false && hasNoVisibleOwner == true &&
|
||||
m_moveSizeWindowInfo.standardWindow == true && m_moveSizeWindowInfo.noVisibleOwner == true) ||
|
||||
FancyZonesUtils::IsWindowMaximized(window))
|
||||
{
|
||||
|
||||
@@ -127,8 +127,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)
|
||||
@@ -140,14 +138,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)
|
||||
@@ -443,9 +437,9 @@ ZoneWindow::ClearSelectedZones() noexcept
|
||||
|
||||
void ZoneWindow::InitializeZoneSets(const std::wstring& parentUniqueId) noexcept
|
||||
{
|
||||
// If there is not defined zone layout for this work area, created default entry.
|
||||
FancyZonesDataInstance().AddDevice(m_uniqueId);
|
||||
if (!parentUniqueId.empty())
|
||||
bool deviceAdded = FancyZonesDataInstance().AddDevice(m_uniqueId);
|
||||
// If the device has been added, check if it should inherit the parent's layout
|
||||
if (deviceAdded && !parentUniqueId.empty())
|
||||
{
|
||||
FancyZonesDataInstance().CloneDeviceInfo(parentUniqueId, m_uniqueId);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "pch.h"
|
||||
#include "KeyDelay.h"
|
||||
|
||||
// NOTE: The destructor should never be called on the DelayThread, i.e. from any of shortPress, longPress or longPressReleased, as it will re-enter the mutex. Even if the mutex is removed it will deadlock because of the join statement
|
||||
KeyDelay::~KeyDelay()
|
||||
{
|
||||
std::unique_lock<std::mutex> l(_queueMutex);
|
||||
|
||||
@@ -613,25 +613,53 @@ bool Shortcut::CheckModifiersKeyboardState(InputInterface& ii) const
|
||||
return true;
|
||||
}
|
||||
|
||||
// Helper method for checking if a key is in a range for cleaner code
|
||||
bool in_range(DWORD key, DWORD a, DWORD b)
|
||||
{
|
||||
return (key >= a && key <= b);
|
||||
}
|
||||
|
||||
// Helper method for checking if a key is equal to a value for cleaner code
|
||||
bool equals(DWORD key, DWORD a)
|
||||
{
|
||||
return (key == a);
|
||||
}
|
||||
|
||||
// Function to check if the key code is to be ignored
|
||||
bool IgnoreKeyCode(DWORD key)
|
||||
{
|
||||
// Ignore mouse buttons. Keeping this could cause a remapping to fail if a mouse button is also pressed at the same time
|
||||
switch (key)
|
||||
{
|
||||
// Ignore mouse buttons. Keeping this could cause a remapping to fail if a mouse button is also pressed at the same time
|
||||
case VK_LBUTTON:
|
||||
case VK_RBUTTON:
|
||||
case VK_MBUTTON:
|
||||
case VK_XBUTTON1:
|
||||
case VK_XBUTTON2:
|
||||
// Ignore these key codes as they are reserved. Used by IME keyboards. More information at https://github.com/microsoft/PowerToys/issues/5225
|
||||
case 0xF0:
|
||||
case 0xF1:
|
||||
case 0xF2:
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
// As per docs: https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes
|
||||
// Undefined keys
|
||||
bool isUndefined = equals(key, 0x07) || in_range(key, 0x0E, 0x0F) || in_range(key, 0x3A, 0x40);
|
||||
|
||||
// Reserved keys
|
||||
bool isReserved = in_range(key, 0x0A, 0x0B) || equals(key, 0x5E) || in_range(key, 0xB8, 0xB9) || in_range(key, 0xC1, 0xD7) || equals(key, 0xE0) || equals(key, VK_NONAME);
|
||||
|
||||
// Unassigned keys
|
||||
bool isUnassigned = in_range(key, 0x88, 0x8F) || in_range(key, 0x97, 0x9F) || in_range(key, 0xD8, 0xDA) || equals(key, 0xE8);
|
||||
|
||||
//OEM Specific keys. Ignore these key codes as some of them are used by IME keyboards. More information at https://github.com/microsoft/PowerToys/issues/5225
|
||||
bool isOEMSpecific = in_range(key, 0x92, 0x96) || equals(key, 0xE1) || in_range(key, 0xE3, 0xE4) || equals(key, 0xE6) || in_range(key, 0xE9, 0xF5);
|
||||
|
||||
if (isUndefined || isReserved || isUnassigned || isOEMSpecific)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Function to check if any keys are pressed down except those in the shortcut
|
||||
|
||||
@@ -355,6 +355,7 @@ void ShortcutControl::createDetectShortcutWindow(winrt::Windows::Foundation::IIn
|
||||
onAccept();
|
||||
});
|
||||
|
||||
// NOTE: UnregisterKeys should never be called on the DelayThread, as it will re-enter the mutex. To avoid this it is run on the dispatcher thread
|
||||
keyboardManagerState.RegisterKeyDelay(
|
||||
VK_RETURN,
|
||||
selectDetectedShortcutAndResetKeys,
|
||||
@@ -367,19 +368,24 @@ void ShortcutControl::createDetectShortcutWindow(winrt::Windows::Foundation::IIn
|
||||
onPressEnter();
|
||||
});
|
||||
},
|
||||
[onReleaseEnter](DWORD) {
|
||||
onReleaseEnter();
|
||||
[onReleaseEnter, detectShortcutBox](DWORD) {
|
||||
detectShortcutBox.Dispatcher().RunAsync(
|
||||
Windows::UI::Core::CoreDispatcherPriority::Normal,
|
||||
[onReleaseEnter]() {
|
||||
onReleaseEnter();
|
||||
});
|
||||
});
|
||||
|
||||
TextBlock cancelButtonText;
|
||||
cancelButtonText.Text(GET_RESOURCE_STRING(IDS_CANCEL_BUTTON));
|
||||
|
||||
Button cancelButton;
|
||||
cancelButton.HorizontalAlignment(HorizontalAlignment::Stretch);
|
||||
cancelButton.Margin({ 2, 2, 2, 2 });
|
||||
cancelButton.Content(cancelButtonText);
|
||||
// Cancel button
|
||||
cancelButton.Click([detectShortcutBox, unregisterKeys, &keyboardManagerState, isSingleKeyWindow, parentWindow](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) {
|
||||
auto onCancel = [&keyboardManagerState,
|
||||
detectShortcutBox,
|
||||
unregisterKeys,
|
||||
isSingleKeyWindow,
|
||||
parentWindow] {
|
||||
detectShortcutBox.Hide();
|
||||
|
||||
// Reset the keyboard manager UI state
|
||||
keyboardManagerState.ResetUIState();
|
||||
if (isSingleKeyWindow)
|
||||
@@ -393,31 +399,27 @@ void ShortcutControl::createDetectShortcutWindow(winrt::Windows::Foundation::IIn
|
||||
keyboardManagerState.SetUIState(KeyboardManagerUIState::EditShortcutsWindowActivated, parentWindow);
|
||||
}
|
||||
unregisterKeys();
|
||||
detectShortcutBox.Hide();
|
||||
};
|
||||
|
||||
Button cancelButton;
|
||||
cancelButton.HorizontalAlignment(HorizontalAlignment::Stretch);
|
||||
cancelButton.Margin({ 2, 2, 2, 2 });
|
||||
cancelButton.Content(cancelButtonText);
|
||||
// Cancel button
|
||||
cancelButton.Click([onCancel](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) {
|
||||
onCancel();
|
||||
});
|
||||
|
||||
// NOTE: UnregisterKeys should never be called on the DelayThread, as it will re-enter the mutex. To avoid this it is run on the dispatcher thread
|
||||
keyboardManagerState.RegisterKeyDelay(
|
||||
VK_ESCAPE,
|
||||
selectDetectedShortcutAndResetKeys,
|
||||
[&keyboardManagerState, detectShortcutBox, unregisterKeys, isSingleKeyWindow, parentWindow](DWORD) {
|
||||
[onCancel, detectShortcutBox](DWORD) {
|
||||
detectShortcutBox.Dispatcher().RunAsync(
|
||||
Windows::UI::Core::CoreDispatcherPriority::Normal,
|
||||
[detectShortcutBox] {
|
||||
detectShortcutBox.Hide();
|
||||
[onCancel] {
|
||||
onCancel();
|
||||
});
|
||||
|
||||
keyboardManagerState.ResetUIState();
|
||||
if (isSingleKeyWindow)
|
||||
{
|
||||
// Revert UI state back to Edit Keyboard window
|
||||
keyboardManagerState.SetUIState(KeyboardManagerUIState::EditKeyboardWindowActivated, parentWindow);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Revert UI state back to Edit Shortcut window
|
||||
keyboardManagerState.SetUIState(KeyboardManagerUIState::EditShortcutsWindowActivated, parentWindow);
|
||||
}
|
||||
unregisterKeys();
|
||||
},
|
||||
nullptr);
|
||||
|
||||
|
||||
@@ -269,6 +269,7 @@ void SingleKeyRemapControl::createDetectKeyWindow(winrt::Windows::Foundation::II
|
||||
onAccept();
|
||||
});
|
||||
|
||||
// NOTE: UnregisterKeys should never be called on the DelayThread, as it will re-enter the mutex. To avoid this it is run on the dispatcher thread
|
||||
keyboardManagerState.RegisterKeyDelay(
|
||||
VK_RETURN,
|
||||
std::bind(&KeyboardManagerState::SelectDetectedRemapKey, &keyboardManagerState, std::placeholders::_1),
|
||||
@@ -281,41 +282,48 @@ void SingleKeyRemapControl::createDetectKeyWindow(winrt::Windows::Foundation::II
|
||||
onPressEnter();
|
||||
});
|
||||
},
|
||||
[onReleaseEnter](DWORD) {
|
||||
onReleaseEnter();
|
||||
[onReleaseEnter, detectRemapKeyBox](DWORD) {
|
||||
detectRemapKeyBox.Dispatcher().RunAsync(
|
||||
Windows::UI::Core::CoreDispatcherPriority::Normal,
|
||||
[onReleaseEnter]() {
|
||||
onReleaseEnter();
|
||||
});
|
||||
});
|
||||
|
||||
TextBlock cancelButtonText;
|
||||
cancelButtonText.Text(GET_RESOURCE_STRING(IDS_CANCEL_BUTTON));
|
||||
|
||||
auto onCancel = [&keyboardManagerState,
|
||||
detectRemapKeyBox,
|
||||
unregisterKeys] {
|
||||
detectRemapKeyBox.Hide();
|
||||
|
||||
// Reset the keyboard manager UI state
|
||||
keyboardManagerState.ResetUIState();
|
||||
// Revert UI state back to Edit Keyboard window
|
||||
keyboardManagerState.SetUIState(KeyboardManagerUIState::EditKeyboardWindowActivated, EditKeyboardWindowHandle);
|
||||
unregisterKeys();
|
||||
};
|
||||
|
||||
Button cancelButton;
|
||||
cancelButton.HorizontalAlignment(HorizontalAlignment::Stretch);
|
||||
cancelButton.Margin({ 2, 2, 2, 2 });
|
||||
cancelButton.Content(cancelButtonText);
|
||||
// Cancel button
|
||||
cancelButton.Click([detectRemapKeyBox, unregisterKeys, &keyboardManagerState](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) {
|
||||
// Reset the keyboard manager UI state
|
||||
keyboardManagerState.ResetUIState();
|
||||
// Revert UI state back to Edit Keyboard window
|
||||
keyboardManagerState.SetUIState(KeyboardManagerUIState::EditKeyboardWindowActivated, EditKeyboardWindowHandle);
|
||||
unregisterKeys();
|
||||
detectRemapKeyBox.Hide();
|
||||
cancelButton.Click([onCancel](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) {
|
||||
onCancel();
|
||||
});
|
||||
|
||||
// NOTE: UnregisterKeys should never be called on the DelayThread, as it will re-enter the mutex. To avoid this it is run on the dispatcher thread
|
||||
keyboardManagerState.RegisterKeyDelay(
|
||||
VK_ESCAPE,
|
||||
std::bind(&KeyboardManagerState::SelectDetectedRemapKey, &keyboardManagerState, std::placeholders::_1),
|
||||
[&keyboardManagerState, detectRemapKeyBox, unregisterKeys](DWORD) {
|
||||
[onCancel, detectRemapKeyBox](DWORD) {
|
||||
detectRemapKeyBox.Dispatcher().RunAsync(
|
||||
Windows::UI::Core::CoreDispatcherPriority::Normal,
|
||||
[detectRemapKeyBox] {
|
||||
detectRemapKeyBox.Hide();
|
||||
[onCancel] {
|
||||
onCancel();
|
||||
});
|
||||
|
||||
keyboardManagerState.ResetUIState();
|
||||
// Revert UI state back to Edit Keyboard window
|
||||
keyboardManagerState.SetUIState(KeyboardManagerUIState::EditKeyboardWindowActivated, EditKeyboardWindowHandle);
|
||||
unregisterKeys();
|
||||
},
|
||||
nullptr);
|
||||
|
||||
|
||||
@@ -330,6 +330,7 @@ namespace Microsoft.Plugin.Program.Programs
|
||||
return ExecutableName;
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "Any error in CreateWin32Program should not prevent other programs from loading.")]
|
||||
private static Win32Program CreateWin32Program(string path)
|
||||
{
|
||||
try
|
||||
@@ -353,78 +354,94 @@ namespace Microsoft.Plugin.Program.Programs
|
||||
{
|
||||
ProgramLogger.Exception($"|Permission denied when trying to load the program from {path}", e, MethodBase.GetCurrentMethod().DeclaringType, path);
|
||||
|
||||
return new Win32Program() { Valid = false, Enabled = false };
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ProgramLogger.Exception($"|An unexpected error occurred in the calling method CreateWin32Program at {path}", e, MethodBase.GetCurrentMethod().DeclaringType, path);
|
||||
|
||||
return new Win32Program() { Valid = false, Enabled = false };
|
||||
}
|
||||
}
|
||||
|
||||
// This function filters Internet Shortcut programs
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "Any error in InternetShortcutProgram should not prevent other programs from loading.")]
|
||||
private static Win32Program InternetShortcutProgram(string path)
|
||||
{
|
||||
string[] lines = FileWrapper.ReadAllLines(path);
|
||||
string iconPath = string.Empty;
|
||||
string urlPath = string.Empty;
|
||||
bool validApp = false;
|
||||
|
||||
Regex internetShortcutURLPrefixes = new Regex(@"^steam:\/\/(rungameid|run)\/|^com\.epicgames\.launcher:\/\/apps\/");
|
||||
|
||||
const string urlPrefix = "URL=";
|
||||
const string iconFilePrefix = "IconFile=";
|
||||
|
||||
foreach (string line in lines)
|
||||
{
|
||||
if (line.StartsWith(urlPrefix, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
urlPath = line.Substring(urlPrefix.Length);
|
||||
|
||||
try
|
||||
{
|
||||
Uri uri = new Uri(urlPath);
|
||||
}
|
||||
catch (UriFormatException e)
|
||||
{
|
||||
// To catch the exception if the uri cannot be parsed.
|
||||
// Link to watson crash: https://watsonportal.microsoft.com/Failure?FailureSearchText=5f871ea7-e886-911f-1b31-131f63f6655b
|
||||
ProgramLogger.Exception($"url could not be parsed", e, MethodBase.GetCurrentMethod().DeclaringType, urlPath);
|
||||
return new Win32Program() { Valid = false, Enabled = false };
|
||||
}
|
||||
|
||||
// To filter out only those steam shortcuts which have 'run' or 'rungameid' as the hostname
|
||||
if (internetShortcutURLPrefixes.Match(urlPath).Success)
|
||||
{
|
||||
validApp = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (line.StartsWith(iconFilePrefix, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
iconPath = line.Substring(iconFilePrefix.Length);
|
||||
}
|
||||
}
|
||||
|
||||
if (!validApp)
|
||||
{
|
||||
return new Win32Program() { Valid = false, Enabled = false };
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var p = new Win32Program
|
||||
string[] lines = FileWrapper.ReadAllLines(path);
|
||||
string iconPath = string.Empty;
|
||||
string urlPath = string.Empty;
|
||||
bool validApp = false;
|
||||
|
||||
Regex internetShortcutURLPrefixes = new Regex(@"^steam:\/\/(rungameid|run)\/|^com\.epicgames\.launcher:\/\/apps\/");
|
||||
|
||||
const string urlPrefix = "URL=";
|
||||
const string iconFilePrefix = "IconFile=";
|
||||
|
||||
foreach (string line in lines)
|
||||
{
|
||||
Name = Path.GetFileNameWithoutExtension(path),
|
||||
ExecutableName = Path.GetFileName(path),
|
||||
IcoPath = iconPath,
|
||||
FullPath = urlPath,
|
||||
UniqueIdentifier = path,
|
||||
ParentDirectory = Directory.GetParent(path).FullName,
|
||||
Valid = true,
|
||||
Enabled = true,
|
||||
AppType = ApplicationType.InternetShortcutApplication,
|
||||
};
|
||||
return p;
|
||||
if (line.StartsWith(urlPrefix, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
urlPath = line.Substring(urlPrefix.Length);
|
||||
|
||||
try
|
||||
{
|
||||
Uri uri = new Uri(urlPath);
|
||||
}
|
||||
catch (UriFormatException e)
|
||||
{
|
||||
// To catch the exception if the uri cannot be parsed.
|
||||
// Link to watson crash: https://watsonportal.microsoft.com/Failure?FailureSearchText=5f871ea7-e886-911f-1b31-131f63f6655b
|
||||
ProgramLogger.Exception($"url could not be parsed", e, MethodBase.GetCurrentMethod().DeclaringType, urlPath);
|
||||
return new Win32Program() { Valid = false, Enabled = false };
|
||||
}
|
||||
|
||||
// To filter out only those steam shortcuts which have 'run' or 'rungameid' as the hostname
|
||||
if (internetShortcutURLPrefixes.Match(urlPath).Success)
|
||||
{
|
||||
validApp = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (line.StartsWith(iconFilePrefix, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
iconPath = line.Substring(iconFilePrefix.Length);
|
||||
}
|
||||
}
|
||||
|
||||
if (!validApp)
|
||||
{
|
||||
return new Win32Program() { Valid = false, Enabled = false };
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var p = new Win32Program
|
||||
{
|
||||
Name = Path.GetFileNameWithoutExtension(path),
|
||||
ExecutableName = Path.GetFileName(path),
|
||||
IcoPath = iconPath,
|
||||
FullPath = urlPath,
|
||||
UniqueIdentifier = path,
|
||||
ParentDirectory = Directory.GetParent(path).FullName,
|
||||
Valid = true,
|
||||
Enabled = true,
|
||||
AppType = ApplicationType.InternetShortcutApplication,
|
||||
};
|
||||
return p;
|
||||
}
|
||||
catch (Exception e) when (e is SecurityException || e is UnauthorizedAccessException)
|
||||
{
|
||||
ProgramLogger.Exception($"|Permission denied when trying to load the program from {path}", e, MethodBase.GetCurrentMethod().DeclaringType, path);
|
||||
|
||||
return new Win32Program() { Valid = false, Enabled = false };
|
||||
}
|
||||
}
|
||||
catch (Exception e) when (e is SecurityException || e is UnauthorizedAccessException)
|
||||
catch (Exception e)
|
||||
{
|
||||
ProgramLogger.Exception($"|Permission denied when trying to load the program from {path}", e, MethodBase.GetCurrentMethod().DeclaringType, path);
|
||||
ProgramLogger.Exception($"|An unexpected error occurred in the calling method InternetShortcutProgram at {path}", e, MethodBase.GetCurrentMethod().DeclaringType, path);
|
||||
|
||||
return new Win32Program() { Valid = false, Enabled = false };
|
||||
}
|
||||
@@ -433,9 +450,9 @@ namespace Microsoft.Plugin.Program.Programs
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "Unsure of what exceptions are caught here while enabling static analysis")]
|
||||
private static Win32Program LnkProgram(string path)
|
||||
{
|
||||
var program = CreateWin32Program(path);
|
||||
try
|
||||
{
|
||||
var program = CreateWin32Program(path);
|
||||
const int MAX_PATH = 260;
|
||||
StringBuilder buffer = new StringBuilder(MAX_PATH);
|
||||
|
||||
@@ -472,13 +489,13 @@ namespace Microsoft.Plugin.Program.Programs
|
||||
// Error caused likely due to trying to get the description of the program
|
||||
catch (Exception e)
|
||||
{
|
||||
ProgramLogger.Exception("An unexpected error occurred in the calling method LnkProgram", e, MethodBase.GetCurrentMethod().DeclaringType, path);
|
||||
ProgramLogger.Exception($"|An unexpected error occurred in the calling method LnkProgram at {path}", e, MethodBase.GetCurrentMethod().DeclaringType, path);
|
||||
|
||||
program.Valid = false;
|
||||
return program;
|
||||
return new Win32Program() { Valid = false, Enabled = false };
|
||||
}
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "Any error in ExeProgram should not prevent other programs from loading.")]
|
||||
private static Win32Program ExeProgram(string path)
|
||||
{
|
||||
try
|
||||
@@ -503,6 +520,12 @@ namespace Microsoft.Plugin.Program.Programs
|
||||
{
|
||||
ProgramLogger.Exception($"|Unable to locate exe file at {path}", e, MethodBase.GetCurrentMethod().DeclaringType, path);
|
||||
|
||||
return new Win32Program() { Valid = false, Enabled = false };
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ProgramLogger.Exception($"|An unexpected error occurred in the calling method ExeProgram at {path}", e, MethodBase.GetCurrentMethod().DeclaringType, path);
|
||||
|
||||
return new Win32Program() { Valid = false, Enabled = false };
|
||||
}
|
||||
}
|
||||
@@ -585,6 +608,7 @@ namespace Microsoft.Plugin.Program.Programs
|
||||
}
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "Minimise the effect of error on other programs")]
|
||||
private static IEnumerable<string> ProgramPaths(string directory, IList<string> suffixes, bool recursiveSearch = true)
|
||||
{
|
||||
if (!Directory.Exists(directory))
|
||||
@@ -617,6 +641,10 @@ namespace Microsoft.Plugin.Program.Programs
|
||||
{
|
||||
ProgramLogger.Exception($"|Permission denied when trying to load programs from {currentDirectory}", e, MethodBase.GetCurrentMethod().DeclaringType, currentDirectory);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ProgramLogger.Exception($"|An unexpected error occurred in the calling method ProgramPaths at {currentDirectory}", e, MethodBase.GetCurrentMethod().DeclaringType, currentDirectory);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
@@ -635,6 +663,10 @@ namespace Microsoft.Plugin.Program.Programs
|
||||
{
|
||||
ProgramLogger.Exception($"|Permission denied when trying to load programs from {currentDirectory}", e, MethodBase.GetCurrentMethod().DeclaringType, currentDirectory);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ProgramLogger.Exception($"|An unexpected error occurred in the calling method ProgramPaths at {currentDirectory}", e, MethodBase.GetCurrentMethod().DeclaringType, currentDirectory);
|
||||
}
|
||||
}
|
||||
while (folderQueue.Any());
|
||||
|
||||
|
||||
@@ -194,14 +194,15 @@ namespace Wox.Core.Plugin
|
||||
{
|
||||
foreach (Result result in results)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(result.QueryTextDisplay))
|
||||
if (string.IsNullOrEmpty(result.QueryTextDisplay))
|
||||
{
|
||||
result.QueryTextDisplay = result.Title;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(query.ActionKeyword))
|
||||
{
|
||||
result.QueryTextDisplay = string.Format("{0} {1}", query.ActionKeyword, result.QueryTextDisplay);
|
||||
}
|
||||
else
|
||||
{
|
||||
result.QueryTextDisplay = string.Format("{0} {1}", query.ActionKeyword, result.Title);
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
|
||||
@@ -15,13 +15,18 @@ namespace Wox.Test
|
||||
[TestFixture]
|
||||
public class PluginManagerTest
|
||||
{
|
||||
[Test]
|
||||
public void QueryForPlugin_SetsActionKeyword_WhenQueryTextDisplayIsSet()
|
||||
[TestCase(">", "dummyQueryText", "dummyTitle", "> dummyQueryText")]
|
||||
[TestCase(">", null, "dummyTitle", "> dummyTitle")]
|
||||
[TestCase(">", "", "dummyTitle", "> dummyTitle")]
|
||||
[TestCase("", "dummyQueryText", "dummyTitle", "dummyQueryText")]
|
||||
[TestCase("", null, "dummyTitle", "dummyTitle")]
|
||||
[TestCase("", "", "dummyTitle", "dummyTitle")]
|
||||
[TestCase(null, "dummyQueryText", "dummyTitle", "dummyQueryText")]
|
||||
[TestCase(null, null, "dummyTitle", "dummyTitle")]
|
||||
[TestCase(null, "", "dummyTitle", "dummyTitle")]
|
||||
public void QueryForPlugin_SetsActionKeyword_WhenQueryTextDisplayIsEmpty(string actionKeyword, string queryTextDisplay, string title, string expectedResult)
|
||||
{
|
||||
// Arrange
|
||||
var actionKeyword = ">";
|
||||
var title = "dummyTitle";
|
||||
var queryTextDisplay = "dummyQueryTextDisplay";
|
||||
var query = new Query
|
||||
{
|
||||
ActionKeyword = actionKeyword,
|
||||
@@ -51,46 +56,7 @@ namespace Wox.Test
|
||||
var queryOutput = PluginManager.QueryForPlugin(pluginPair, query);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(string.Format("{0} {1}", ">", queryTextDisplay), queryOutput[0].QueryTextDisplay);
|
||||
}
|
||||
|
||||
[TestCase("")]
|
||||
[TestCase(null)]
|
||||
public void QueryForPlugin_SetsActionKeyword_WhenQueryTextDisplayIsEmpty(string queryTextDisplay)
|
||||
{
|
||||
// Arrange
|
||||
var actionKeyword = ">";
|
||||
var title = "dummyTitle";
|
||||
var query = new Query
|
||||
{
|
||||
ActionKeyword = actionKeyword,
|
||||
};
|
||||
var metadata = new PluginMetadata
|
||||
{
|
||||
ID = "dummyName",
|
||||
IcoPath = "dummyIcoPath",
|
||||
ExecuteFileName = "dummyExecuteFileName",
|
||||
PluginDirectory = "dummyPluginDirectory",
|
||||
};
|
||||
var result = new Result()
|
||||
{
|
||||
QueryTextDisplay = queryTextDisplay,
|
||||
Title = title,
|
||||
};
|
||||
var results = new List<Result>() { result };
|
||||
var pluginMock = new Mock<IPlugin>();
|
||||
pluginMock.Setup(r => r.Query(query)).Returns(results);
|
||||
var pluginPair = new PluginPair
|
||||
{
|
||||
Plugin = pluginMock.Object,
|
||||
Metadata = metadata,
|
||||
};
|
||||
|
||||
// Act
|
||||
var queryOutput = PluginManager.QueryForPlugin(pluginPair, query);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(string.Format("{0} {1}", ">", title), queryOutput[0].QueryTextDisplay);
|
||||
Assert.AreEqual(expectedResult, queryOutput[0].QueryTextDisplay);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#include <pch.h>
|
||||
#include "pch.h"
|
||||
#include "registry_wrapper.h"
|
||||
|
||||
namespace PowerPreviewSettings
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#pragma once
|
||||
#include <pch.h>
|
||||
#include <string>
|
||||
#include "Generated Files/resource.h"
|
||||
#include <settings_objects.h>
|
||||
|
||||
@@ -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() noexcept
|
||||
{
|
||||
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) noexcept
|
||||
{
|
||||
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, ¤t_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;
|
||||
}
|
||||
@@ -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) noexcept;
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
@@ -0,0 +1,670 @@
|
||||
#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
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// 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;
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
#pragma once
|
||||
|
||||
#include "stdafx.h"
|
||||
|
||||
#include <SerializedSharedMemory.h>
|
||||
#include <CameraStateUpdateChannels.h>
|
||||
|
||||
#include <common/VideoCaptureDeviceList.h>
|
||||
|
||||
class SimpleMediaSource;
|
||||
|
||||
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;
|
||||
|
||||
VideoCaptureDeviceList _cameraList;
|
||||
ComPtr<IMFSourceReader> _sourceCamera;
|
||||
ComPtr<IMFSample> _overlayImage;
|
||||
|
||||
ComPtr<IMFSample> _blackImage;
|
||||
|
||||
std::optional<SerializedSharedMemory> _settingsUpdateChannel;
|
||||
std::optional<std::wstring> _currentSourceCameraName;
|
||||
};
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
LIBRARY
|
||||
|
||||
EXPORTS
|
||||
DllGetActivationFactory PRIVATE
|
||||
DllGetClassObject PRIVATE
|
||||
DllCanUnloadNow PRIVATE
|
||||
@@ -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>
|
||||
@@ -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"
|
||||
@@ -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_
|
||||
|
After Width: | Height: | Size: 3.9 KiB |
@@ -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 |
|
After Width: | Height: | Size: 3.8 KiB |
@@ -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 |
|
After Width: | Height: | Size: 3.7 KiB |
@@ -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 |
|
After Width: | Height: | Size: 3.8 KiB |
@@ -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 |
|
After Width: | Height: | Size: 3.4 KiB |
@@ -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 |
|
After Width: | Height: | Size: 3.5 KiB |
@@ -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 |
|
After Width: | Height: | Size: 3.6 KiB |
@@ -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 |
|
After Width: | Height: | Size: 3.5 KiB |
@@ -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 |
|
After Width: | Height: | Size: 3.5 KiB |