mirror of
https://github.com/microsoft/PowerToys.git
synced 2025-01-18 22:43:31 +08:00
[VCM] Track newly added microphones (#16199)
* [VCM] Track newly added microphones when [All] is selected in the settings * [VCM] handle case when no mics are left * fixup: fix crashes onNotify * fixup: fix build
This commit is contained in:
parent
2e3a2b3f96
commit
176f2c2870
3
.github/actions/spell-check/expect.txt
vendored
3
.github/actions/spell-check/expect.txt
vendored
@ -498,6 +498,7 @@ dxgi
|
||||
dxgiformat
|
||||
dxguid
|
||||
ecount
|
||||
EData
|
||||
EDITKEYBOARD
|
||||
editkeyboardwindow
|
||||
editorconfig
|
||||
@ -527,6 +528,7 @@ EREOF
|
||||
EResize
|
||||
ERRORMESSAGE
|
||||
ERRORTITLE
|
||||
ERole
|
||||
ESettings
|
||||
esize
|
||||
esrp
|
||||
@ -1551,6 +1553,7 @@ programdata
|
||||
PROGRAMFILES
|
||||
projectname
|
||||
PROPBAG
|
||||
PROPERTYKEY
|
||||
propkey
|
||||
propvarutil
|
||||
prvpane
|
||||
|
@ -132,7 +132,7 @@ public
|
||||
auto names = gcnew List<String ^>();
|
||||
for (const auto& device : MicrophoneDevice::getAllActive())
|
||||
{
|
||||
names->Add(gcnew String(device.name().data()));
|
||||
names->Add(gcnew String(device->name().data()));
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
@ -0,0 +1,78 @@
|
||||
#include "pch.h"
|
||||
|
||||
#include "AudioDeviceNotificationClient.h"
|
||||
|
||||
AudioDeviceNotificationClient::AudioDeviceNotificationClient()
|
||||
{
|
||||
(void)CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&_deviceEnumerator));
|
||||
if (!_deviceEnumerator)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (FAILED(_deviceEnumerator->RegisterEndpointNotificationCallback(this)))
|
||||
{
|
||||
_deviceEnumerator->Release();
|
||||
_deviceEnumerator = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
AudioDeviceNotificationClient::~AudioDeviceNotificationClient()
|
||||
{
|
||||
if (!_deviceEnumerator)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_deviceEnumerator->UnregisterEndpointNotificationCallback(this);
|
||||
_deviceEnumerator->Release();
|
||||
}
|
||||
|
||||
ULONG AudioDeviceNotificationClient::AddRef()
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
ULONG AudioDeviceNotificationClient::Release()
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
HRESULT AudioDeviceNotificationClient::QueryInterface(REFIID, void**)
|
||||
{
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT AudioDeviceNotificationClient::OnPropertyValueChanged(LPCWSTR, const PROPERTYKEY)
|
||||
{
|
||||
_deviceConfigurationChanged = true;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT AudioDeviceNotificationClient::OnDeviceAdded(LPCWSTR)
|
||||
{
|
||||
_deviceConfigurationChanged = true;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT AudioDeviceNotificationClient::OnDeviceRemoved(LPCWSTR)
|
||||
{
|
||||
_deviceConfigurationChanged = true;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT AudioDeviceNotificationClient::OnDeviceStateChanged(LPCWSTR, DWORD)
|
||||
{
|
||||
_deviceConfigurationChanged = true;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT AudioDeviceNotificationClient::OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR)
|
||||
{
|
||||
if (role == eConsole && (flow == eCapture || flow == eAll))
|
||||
{
|
||||
_deviceConfigurationChanged = true;
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
|
||||
#include <MMDeviceAPI.h>
|
||||
|
||||
struct AudioDeviceNotificationClient : IMMNotificationClient
|
||||
{
|
||||
AudioDeviceNotificationClient();
|
||||
~AudioDeviceNotificationClient();
|
||||
|
||||
bool PullPendingNotifications()
|
||||
{
|
||||
const bool result = _deviceConfigurationChanged;
|
||||
_deviceConfigurationChanged = false;
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
ULONG AddRef() override;
|
||||
ULONG Release() override;
|
||||
HRESULT QueryInterface(REFIID, void**) override;
|
||||
HRESULT OnPropertyValueChanged(LPCWSTR, const PROPERTYKEY) override;
|
||||
HRESULT OnDeviceAdded(LPCWSTR) override;
|
||||
HRESULT OnDeviceRemoved(LPCWSTR) override;
|
||||
HRESULT OnDeviceStateChanged(LPCWSTR, DWORD) override;
|
||||
HRESULT OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR) override;
|
||||
|
||||
IMMDeviceEnumerator* _deviceEnumerator = nullptr;
|
||||
|
||||
bool _deviceConfigurationChanged = false;
|
||||
};
|
@ -126,6 +126,12 @@ LRESULT Toolbar::WindowProcessMessages(HWND hwnd, UINT msg, WPARAM wparam, LPARA
|
||||
}
|
||||
case WM_TIMER:
|
||||
{
|
||||
if (toolbar->audioConfChangesNotifier.PullPendingNotifications())
|
||||
{
|
||||
instance->onMicrophoneConfigurationChanged();
|
||||
}
|
||||
toolbar->microphoneMuted = instance->getMicrophoneMuteState();
|
||||
|
||||
if (toolbar->generalSettingsUpdateScheduled)
|
||||
{
|
||||
instance->onGeneralSettingsChanged();
|
||||
|
@ -6,6 +6,8 @@
|
||||
|
||||
#include <common/Display/monitors.h>
|
||||
|
||||
#include "AudioDeviceNotificationClient.h"
|
||||
|
||||
struct ToolbarImages
|
||||
{
|
||||
Gdiplus::Image* camOnMicOn = nullptr;
|
||||
@ -43,6 +45,7 @@ private:
|
||||
|
||||
ToolbarImages darkImages;
|
||||
ToolbarImages lightImages;
|
||||
AudioDeviceNotificationClient audioConfChangesNotifier;
|
||||
|
||||
bool cameraMuted = false;
|
||||
bool cameraInUse = false;
|
||||
|
@ -111,6 +111,7 @@ xcopy /y /I "$(ProjectDir)black.bmp*" "$(OutDir)"</Command>
|
||||
</PostBuildEvent>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="AudioDeviceNotificationClient.h" />
|
||||
<ClInclude Include="framework.h" />
|
||||
<ClInclude Include="Toolbar.h" />
|
||||
<ClInclude Include="pch.h" />
|
||||
@ -118,6 +119,7 @@ xcopy /y /I "$(ProjectDir)black.bmp*" "$(OutDir)"</Command>
|
||||
<ClInclude Include="VideoConferenceModule.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="AudioDeviceNotificationClient.cpp" />
|
||||
<ClCompile Include="dllmain.cpp" />
|
||||
<ClCompile Include="trace.cpp" />
|
||||
<ClCompile Include="Toolbar.cpp" />
|
||||
|
@ -6,6 +6,7 @@
|
||||
<ClCompile Include="pch.cpp" />
|
||||
<ClCompile Include="VideoConferenceModule.cpp" />
|
||||
<ClCompile Include="trace.cpp" />
|
||||
<ClCompile Include="AudioDeviceNotificationClient.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="framework.h" />
|
||||
@ -13,6 +14,7 @@
|
||||
<ClInclude Include="pch.h" />
|
||||
<ClInclude Include="VideoConferenceModule.h" />
|
||||
<ClInclude Include="trace.h" />
|
||||
<ClInclude Include="AudioDeviceNotificationClient.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Image Include="black.bmp" />
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include <shellapi.h>
|
||||
|
||||
#include <filesystem>
|
||||
#include <unordered_set>
|
||||
|
||||
#include <common/debug_control.h>
|
||||
#include <common/SettingsAPI/settings_helpers.h>
|
||||
@ -51,20 +52,22 @@ void VideoConferenceModule::reverseMicrophoneMute()
|
||||
bool muted = false;
|
||||
for (auto& controlledMic : instance->_controlledMicrophones)
|
||||
{
|
||||
const bool was_muted = controlledMic.muted();
|
||||
controlledMic.toggle_muted();
|
||||
const bool was_muted = controlledMic->muted();
|
||||
controlledMic->toggle_muted();
|
||||
muted = muted || !was_muted;
|
||||
}
|
||||
if (muted)
|
||||
{
|
||||
Trace::MicrophoneMuted();
|
||||
}
|
||||
instance->_mic_muted_state_during_disconnect = !instance->_mic_muted_state_during_disconnect;
|
||||
|
||||
toolbar.setMicrophoneMute(muted);
|
||||
}
|
||||
|
||||
bool VideoConferenceModule::getMicrophoneMuteState()
|
||||
{
|
||||
return instance->_microphoneTrackedInUI ? instance->_microphoneTrackedInUI->muted() : false;
|
||||
return instance->_microphoneTrackedInUI ? instance->_microphoneTrackedInUI->muted() : instance->_mic_muted_state_during_disconnect;
|
||||
}
|
||||
|
||||
void VideoConferenceModule::reverseVirtualCameraMuteState()
|
||||
@ -268,6 +271,46 @@ void VideoConferenceModule::onModuleSettingsChanged()
|
||||
}
|
||||
}
|
||||
|
||||
void VideoConferenceModule::onMicrophoneConfigurationChanged()
|
||||
{
|
||||
if (!_controllingAllMics)
|
||||
{
|
||||
// Don't care if we don't control all the mics
|
||||
return;
|
||||
}
|
||||
|
||||
const bool mutedStateForNewMics = _microphoneTrackedInUI ? _microphoneTrackedInUI->muted() : _mic_muted_state_during_disconnect;
|
||||
std::unordered_set<std::wstring_view> currentlyTrackedMicsIds;
|
||||
for (const auto& controlledMic : _controlledMicrophones)
|
||||
{
|
||||
currentlyTrackedMicsIds.emplace(controlledMic->id());
|
||||
}
|
||||
|
||||
auto allMics = MicrophoneDevice::getAllActive();
|
||||
for (auto& newMic : allMics)
|
||||
{
|
||||
if (currentlyTrackedMicsIds.contains(newMic->id()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (mutedStateForNewMics)
|
||||
{
|
||||
newMic->set_muted(true);
|
||||
}
|
||||
|
||||
_controlledMicrophones.emplace_back(std::move(newMic));
|
||||
}
|
||||
// Restore invalidated pointer
|
||||
_microphoneTrackedInUI = controlledDefaultMic();
|
||||
if (_microphoneTrackedInUI)
|
||||
{
|
||||
_microphoneTrackedInUI->set_mute_changed_callback([](const bool muted) {
|
||||
toolbar.setMicrophoneMute(muted);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
VideoConferenceModule::VideoConferenceModule() :
|
||||
_generalSettingsWatcher{ PTSettingsHelper::get_powertoys_general_save_file_location(), [this] {
|
||||
toolbar.scheduleGeneralSettingsUpdate();
|
||||
@ -388,47 +431,56 @@ void VideoConferenceModule::updateControlledMicrophones(const std::wstring_view
|
||||
{
|
||||
for (auto& controlledMic : _controlledMicrophones)
|
||||
{
|
||||
controlledMic.set_muted(false);
|
||||
controlledMic->set_muted(false);
|
||||
}
|
||||
_controlledMicrophones.clear();
|
||||
_microphoneTrackedInUI = nullptr;
|
||||
auto allMics = MicrophoneDevice::getAllActive();
|
||||
if (new_mic == L"[All]")
|
||||
{
|
||||
_controllingAllMics = true;
|
||||
_controlledMicrophones = std::move(allMics);
|
||||
if (auto defaultMic = MicrophoneDevice::getDefault())
|
||||
{
|
||||
for (auto& controlledMic : _controlledMicrophones)
|
||||
{
|
||||
if (controlledMic.id() == defaultMic->id())
|
||||
{
|
||||
_microphoneTrackedInUI = &controlledMic;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
_microphoneTrackedInUI = controlledDefaultMic();
|
||||
}
|
||||
else
|
||||
{
|
||||
_controllingAllMics = false;
|
||||
for (auto& controlledMic : allMics)
|
||||
{
|
||||
if (controlledMic.name() == new_mic)
|
||||
if (controlledMic->name() == new_mic)
|
||||
{
|
||||
_controlledMicrophones.emplace_back(std::move(controlledMic));
|
||||
_microphoneTrackedInUI = &_controlledMicrophones[0];
|
||||
_microphoneTrackedInUI = _controlledMicrophones[0].get();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_microphoneTrackedInUI)
|
||||
{
|
||||
_microphoneTrackedInUI->set_mute_changed_callback([&](const bool muted) {
|
||||
_microphoneTrackedInUI->set_mute_changed_callback([](const bool muted) {
|
||||
toolbar.setMicrophoneMute(muted);
|
||||
});
|
||||
toolbar.setMicrophoneMute(_microphoneTrackedInUI->muted());
|
||||
}
|
||||
}
|
||||
|
||||
MicrophoneDevice* VideoConferenceModule::controlledDefaultMic()
|
||||
{
|
||||
if (auto defaultMic = MicrophoneDevice::getDefault())
|
||||
{
|
||||
for (auto& controlledMic : _controlledMicrophones)
|
||||
{
|
||||
if (controlledMic->id() == defaultMic->id())
|
||||
{
|
||||
return controlledMic.get();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void toggleProxyCamRegistration(const bool enable)
|
||||
{
|
||||
if (!is_process_elevated())
|
||||
|
@ -61,10 +61,14 @@ public:
|
||||
|
||||
void onGeneralSettingsChanged();
|
||||
void onModuleSettingsChanged();
|
||||
void onMicrophoneConfigurationChanged();
|
||||
|
||||
private:
|
||||
|
||||
void init_settings();
|
||||
void updateControlledMicrophones(const std::wstring_view new_mic);
|
||||
MicrophoneDevice* controlledDefaultMic();
|
||||
|
||||
// all callback methods and used by callback have to be static
|
||||
static LRESULT CALLBACK LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam);
|
||||
static bool isKeyPressed(unsigned int keyCode);
|
||||
@ -73,7 +77,9 @@ private:
|
||||
static HHOOK hook_handle;
|
||||
bool _enabled = false;
|
||||
|
||||
std::vector<MicrophoneDevice> _controlledMicrophones;
|
||||
bool _mic_muted_state_during_disconnect = false;
|
||||
bool _controllingAllMics = false;
|
||||
std::vector<std::unique_ptr<MicrophoneDevice>> _controlledMicrophones;
|
||||
MicrophoneDevice* _microphoneTrackedInUI = nullptr;
|
||||
|
||||
std::optional<SerializedSharedMemory> _imageOverlayChannel;
|
||||
|
@ -869,6 +869,13 @@ VideoCaptureProxyFilter::~VideoCaptureProxyFilter()
|
||||
|
||||
_worker_cv.notify_one();
|
||||
_worker_thread.join();
|
||||
if (_settingsUpdateChannel)
|
||||
{
|
||||
_settingsUpdateChannel->access([](auto settingsMemory) {
|
||||
auto settings = reinterpret_cast<CameraSettingsUpdateChannel*>(settingsMemory._data);
|
||||
settings->cameraInUse = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
VideoCaptureProxyFilter::SyncedSettings VideoCaptureProxyFilter::SyncCurrentSettings()
|
||||
|
@ -76,34 +76,34 @@ void MicrophoneDevice::set_mute_changed_callback(mute_changed_cb_t callback) noe
|
||||
_endpoint->RegisterControlChangeNotify(_notifier.get());
|
||||
}
|
||||
|
||||
std::optional<MicrophoneDevice> MicrophoneDevice::getDefault()
|
||||
std::unique_ptr<MicrophoneDevice> MicrophoneDevice::getDefault()
|
||||
{
|
||||
auto deviceEnumerator = wil::CoCreateInstanceNoThrow<MMDeviceEnumerator, IMMDeviceEnumerator>();
|
||||
if (!deviceEnumerator)
|
||||
{
|
||||
LOG("MicrophoneDevice::getDefault MMDeviceEnumerator returned null");
|
||||
return std::nullopt;
|
||||
return nullptr;
|
||||
}
|
||||
wil::com_ptr_nothrow<IMMDevice> captureDevice;
|
||||
deviceEnumerator->GetDefaultAudioEndpoint(eCapture, eCommunications, &captureDevice);
|
||||
if (!captureDevice)
|
||||
{
|
||||
LOG("MicrophoneDevice::getDefault captureDevice is null");
|
||||
return std::nullopt;
|
||||
return nullptr;
|
||||
}
|
||||
wil::com_ptr_nothrow<IAudioEndpointVolume> microphoneEndpoint;
|
||||
captureDevice->Activate(__uuidof(IAudioEndpointVolume), CLSCTX_INPROC_SERVER, nullptr, reinterpret_cast<LPVOID*>(µphoneEndpoint));
|
||||
if (!microphoneEndpoint)
|
||||
{
|
||||
LOG("MicrophoneDevice::getDefault captureDevice is null");
|
||||
return std::nullopt;
|
||||
return nullptr;
|
||||
}
|
||||
return std::make_optional<MicrophoneDevice>(std::move(captureDevice), std::move(microphoneEndpoint));
|
||||
return std::make_unique<MicrophoneDevice>(std::move(captureDevice), std::move(microphoneEndpoint));
|
||||
}
|
||||
|
||||
std::vector<MicrophoneDevice> MicrophoneDevice::getAllActive()
|
||||
std::vector<std::unique_ptr<MicrophoneDevice>> MicrophoneDevice::getAllActive()
|
||||
{
|
||||
std::vector<MicrophoneDevice> microphoneDevices;
|
||||
std::vector<std::unique_ptr<MicrophoneDevice>> microphoneDevices;
|
||||
auto deviceEnumerator = wil::CoCreateInstanceNoThrow<MMDeviceEnumerator, IMMDeviceEnumerator>();
|
||||
if (!deviceEnumerator)
|
||||
{
|
||||
@ -135,7 +135,7 @@ std::vector<MicrophoneDevice> MicrophoneDevice::getAllActive()
|
||||
{
|
||||
continue;
|
||||
}
|
||||
microphoneDevices.emplace_back(std::move(device), std::move(microphoneEndpoint));
|
||||
microphoneDevices.push_back(std::make_unique<MicrophoneDevice>(std::move(device), std::move(microphoneEndpoint)));
|
||||
}
|
||||
return microphoneDevices;
|
||||
}
|
||||
@ -147,6 +147,8 @@ MicrophoneDevice::VolumeNotifier::VolumeNotifier(MicrophoneDevice* subscribedDev
|
||||
|
||||
HRESULT __stdcall MicrophoneDevice::VolumeNotifier::OnNotify(PAUDIO_VOLUME_NOTIFICATION_DATA data)
|
||||
{
|
||||
_subscribedDevice->_mute_changed_callback(data->bMuted);
|
||||
if (_subscribedDevice && _subscribedDevice->_mute_changed_callback)
|
||||
_subscribedDevice->_mute_changed_callback(data->bMuted);
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
@ -10,7 +10,6 @@
|
||||
#include <wil/resource.h>
|
||||
#include <wil/com.h>
|
||||
|
||||
|
||||
#include <string_view>
|
||||
|
||||
#include <optional>
|
||||
@ -47,7 +46,8 @@ private:
|
||||
constexpr static inline std::wstring_view FALLBACK_ID = L"UNKNOWN_ID";
|
||||
|
||||
public:
|
||||
MicrophoneDevice(MicrophoneDevice&&) noexcept = default;
|
||||
MicrophoneDevice(MicrophoneDevice&&) noexcept = delete;
|
||||
MicrophoneDevice(const MicrophoneDevice&) noexcept = delete;
|
||||
MicrophoneDevice(wil::com_ptr_nothrow<IMMDevice> device, wil::com_ptr_nothrow<IAudioEndpointVolume> endpoint);
|
||||
~MicrophoneDevice();
|
||||
|
||||
@ -60,6 +60,6 @@ public:
|
||||
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();
|
||||
static std::unique_ptr<MicrophoneDevice> getDefault();
|
||||
static std::vector<std::unique_ptr<MicrophoneDevice>> getAllActive();
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user