From 6f6ef68cc4e0c0722380a42624402346c8ac94d1 Mon Sep 17 00:00:00 2001 From: webadderall <131426131+webadderall@users.noreply.github.com> Date: Thu, 26 Mar 2026 11:17:39 +1100 Subject: [PATCH] fix(windows): honor selected microphone in native capture --- electron/ipc/handlers.ts | 3 ++ electron/native/wgc-capture/src/main.cpp | 7 ++- .../wgc-capture/src/wasapi_loopback.cpp | 52 +++++++++++++++++-- .../native/wgc-capture/src/wasapi_loopback.h | 6 ++- 4 files changed, 62 insertions(+), 6 deletions(-) diff --git a/electron/ipc/handlers.ts b/electron/ipc/handlers.ts index ebf73e6b..0a3d6d8c 100644 --- a/electron/ipc/handlers.ts +++ b/electron/ipc/handlers.ts @@ -3041,6 +3041,9 @@ body{background:transparent;overflow:hidden;width:100vw;height:100vh} const micPath = path.join(recordingsDir, `recording-${timestamp}.mic.wav`) config.captureMic = true config.micOutputPath = micPath + if (options.microphoneDeviceId) { + config.micDeviceId = options.microphoneDeviceId + } if (options.microphoneLabel) { config.micDeviceName = options.microphoneLabel } diff --git a/electron/native/wgc-capture/src/main.cpp b/electron/native/wgc-capture/src/main.cpp index a0f24d71..9bbaed33 100644 --- a/electron/native/wgc-capture/src/main.cpp +++ b/electron/native/wgc-capture/src/main.cpp @@ -29,6 +29,7 @@ struct CaptureConfig { std::string outputPath; std::string audioOutputPath; std::string micOutputPath; + std::string micDeviceId; std::string micDeviceName; int fps = 60; int width = 0; @@ -113,6 +114,7 @@ static bool parseSimpleJson(const std::string& json, CaptureConfig& config) { config.audioOutputPath = findString("audioOutputPath"); config.micOutputPath = findString("micOutputPath"); + config.micDeviceId = findString("micDeviceId"); config.micDeviceName = findString("micDeviceName"); auto findBool = [&](const std::string& key) -> bool { @@ -276,7 +278,10 @@ int main(int argc, char* argv[]) { } if (config.captureMic && !config.micOutputPath.empty()) { - micInitialized = micCapture.initializeMic(config.micOutputPath, config.micDeviceName); + micInitialized = micCapture.initializeMic( + config.micOutputPath, + config.micDeviceId, + config.micDeviceName); if (!micInitialized) { std::cerr << "WARNING: Failed to initialize WASAPI mic capture" << std::endl; } diff --git a/electron/native/wgc-capture/src/wasapi_loopback.cpp b/electron/native/wgc-capture/src/wasapi_loopback.cpp index 8ae15ad1..b7b1cc06 100644 --- a/electron/native/wgc-capture/src/wasapi_loopback.cpp +++ b/electron/native/wgc-capture/src/wasapi_loopback.cpp @@ -29,6 +29,36 @@ static std::wstring utf8ToWide(const std::string& str) { return wstr; } +IMMDevice* WasapiCapture::findCaptureDeviceById(const std::wstring& targetId) { + IMMDeviceCollection* collection = nullptr; + HRESULT hr = enumerator_->EnumAudioEndpoints(eCapture, DEVICE_STATE_ACTIVE, &collection); + if (FAILED(hr)) return nullptr; + + UINT count = 0; + collection->GetCount(&count); + + for (UINT i = 0; i < count; i++) { + IMMDevice* dev = nullptr; + collection->Item(i, &dev); + + LPWSTR deviceId = nullptr; + hr = dev->GetId(&deviceId); + if (SUCCEEDED(hr) && deviceId) { + const bool matches = targetId == deviceId; + CoTaskMemFree(deviceId); + if (matches) { + collection->Release(); + return dev; + } + } + + dev->Release(); + } + + collection->Release(); + return nullptr; +} + IMMDevice* WasapiCapture::findCaptureDeviceByName(const std::wstring& targetName) { IMMDeviceCollection* collection = nullptr; HRESULT hr = enumerator_->EnumAudioEndpoints(eCapture, DEVICE_STATE_ACTIVE, &collection); @@ -37,6 +67,8 @@ IMMDevice* WasapiCapture::findCaptureDeviceByName(const std::wstring& targetName UINT count = 0; collection->GetCount(&count); + IMMDevice* partialMatch = nullptr; + for (UINT i = 0; i < count; i++) { IMMDevice* dev = nullptr; collection->Item(i, &dev); @@ -50,15 +82,21 @@ IMMDevice* WasapiCapture::findCaptureDeviceByName(const std::wstring& targetName PropVariantClear(&pv); store->Release(); - if (name.find(targetName) != std::wstring::npos || targetName.find(name) != std::wstring::npos) { + if (name == targetName) { collection->Release(); return dev; } + + if (!partialMatch && (name.find(targetName) != std::wstring::npos || targetName.find(name) != std::wstring::npos)) { + partialMatch = dev; + continue; + } + dev->Release(); } collection->Release(); - return nullptr; + return partialMatch; } bool WasapiCapture::initializeLoopback(const std::string& outputPath) { @@ -76,7 +114,10 @@ bool WasapiCapture::initializeLoopback(const std::string& outputPath) { return initializeCommon(); } -bool WasapiCapture::initializeMic(const std::string& outputPath, const std::string& deviceName) { +bool WasapiCapture::initializeMic( + const std::string& outputPath, + const std::string& deviceId, + const std::string& deviceName) { outputPath_ = outputPath; streamFlags_ = 0; @@ -85,7 +126,10 @@ bool WasapiCapture::initializeMic(const std::string& outputPath, const std::stri IID_IMMDeviceEnumerator_, reinterpret_cast(&enumerator_)); if (FAILED(hr)) return false; - if (!deviceName.empty()) { + if (!deviceId.empty()) { + device_ = findCaptureDeviceById(utf8ToWide(deviceId)); + } + if (!device_ && !deviceName.empty()) { device_ = findCaptureDeviceByName(utf8ToWide(deviceName)); } if (!device_) { diff --git a/electron/native/wgc-capture/src/wasapi_loopback.h b/electron/native/wgc-capture/src/wasapi_loopback.h index a4facf13..e9b9c21a 100644 --- a/electron/native/wgc-capture/src/wasapi_loopback.h +++ b/electron/native/wgc-capture/src/wasapi_loopback.h @@ -14,7 +14,10 @@ public: ~WasapiCapture(); bool initializeLoopback(const std::string& outputPath); - bool initializeMic(const std::string& outputPath, const std::string& deviceName = ""); + bool initializeMic( + const std::string& outputPath, + const std::string& deviceId = "", + const std::string& deviceName = ""); bool start(); bool pause(); bool resume(); @@ -24,6 +27,7 @@ private: bool initializeCommon(); void captureThread(); bool writeWavHeader(HANDLE file, DWORD dataSize); + IMMDevice* findCaptureDeviceById(const std::wstring& id); IMMDevice* findCaptureDeviceByName(const std::wstring& name); std::string outputPath_;