-
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
WASAPI: Implement node addition/removal callback(s)
- Loading branch information
1 parent
8fd1818
commit c658b66
Showing
7 changed files
with
313 additions
and
79 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
// Copyright 2024 The Mumble Developers. All rights reserved. | ||
// Use of this source code is governed by a BSD-style license | ||
// that can be found in the LICENSE file at the root of the | ||
// Mumble source tree or at <https://www.mumble.info/LICENSE>. | ||
|
||
#include "Device.hpp" | ||
|
||
#include "Node.h" | ||
|
||
#include <cstdlib> | ||
#include <cstring> | ||
|
||
#include <Windows.h> | ||
|
||
#include <audioclient.h> | ||
#include <functiondiscoverykeys.h> | ||
#include <mmdeviceapi.h> | ||
|
||
using Direction = CrossAudio_Direction; | ||
|
||
namespace wasapi { | ||
static Direction getDirection(IMMDevice &device) { | ||
IMMEndpoint *endpoint; | ||
if (device.QueryInterface(__uuidof(IMMEndpoint), reinterpret_cast< void ** >(&endpoint)) != S_OK) { | ||
return CROSSAUDIO_DIR_NONE; | ||
} | ||
|
||
EDataFlow dataflow; | ||
if (endpoint->GetDataFlow(&dataflow) != S_OK) { | ||
dataflow = EDataFlow_enum_count; | ||
} | ||
|
||
endpoint->Release(); | ||
|
||
switch (dataflow) { | ||
case eRender: | ||
return CROSSAUDIO_DIR_OUT; | ||
case eCapture: | ||
return CROSSAUDIO_DIR_IN; | ||
case eAll: | ||
return CROSSAUDIO_DIR_BOTH; | ||
case EDataFlow_enum_count: | ||
break; | ||
} | ||
|
||
return CROSSAUDIO_DIR_NONE; | ||
} | ||
|
||
static char *getID(IMMDevice &device) { | ||
wchar_t *deviceID; | ||
if (device.GetId(&deviceID) != S_OK) { | ||
return nullptr; | ||
} | ||
|
||
char *id = utf16To8(deviceID); | ||
|
||
CoTaskMemFree(deviceID); | ||
|
||
return id; | ||
} | ||
|
||
static char *getName(IMMDevice &device) { | ||
IPropertyStore *store; | ||
if (device.OpenPropertyStore(STGM_READ, &store) != S_OK) { | ||
return nullptr; | ||
} | ||
|
||
char *name = nullptr; | ||
|
||
PROPVARIANT varName{}; | ||
if (store->GetValue(PKEY_Device_FriendlyName, &varName) == S_OK) { | ||
name = utf16To8(varName.pwszVal); | ||
PropVariantClear(&varName); | ||
} | ||
|
||
store->Release(); | ||
|
||
return name; | ||
} | ||
|
||
bool populateNode(Node &node, IMMDevice &device, const wchar_t *id) { | ||
if (!(node.id = id ? utf16To8(id) : getID(device))) { | ||
return false; | ||
} | ||
|
||
node.name = getName(device); | ||
node.direction = getDirection(device); | ||
|
||
return true; | ||
} | ||
|
||
char *utf16To8(const wchar_t *utf16) { | ||
const auto utf16Size = static_cast< int >(wcslen(utf16) + 1); | ||
|
||
const auto utf8Size = WideCharToMultiByte(CP_UTF8, 0, utf16, utf16Size, nullptr, 0, nullptr, nullptr); | ||
auto utf8 = static_cast< char *>(malloc(utf8Size)); | ||
if (WideCharToMultiByte(CP_UTF8, 0, utf16, utf16Size, utf8, utf8Size, nullptr, nullptr) <= 0) { | ||
free(utf8); | ||
return nullptr; | ||
} | ||
|
||
return utf8; | ||
} | ||
|
||
wchar_t *utf8To16(const char *utf8) { | ||
const auto utf8Size = static_cast< int >(strlen(utf8) + 1); | ||
|
||
const int utf16Size = sizeof(wchar_t) * MultiByteToWideChar(CP_UTF8, 0, utf8, utf8Size, nullptr, 0); | ||
auto utf16 = static_cast< wchar_t *>(malloc(utf16Size)); | ||
if (MultiByteToWideChar(CP_UTF8, 0, utf8, utf8Size, utf16, utf16Size / sizeof(wchar_t)) <= 0) { | ||
free(utf16); | ||
return nullptr; | ||
} | ||
|
||
return utf16; | ||
} | ||
} // namespace wasapi |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
// Copyright 2024 The Mumble Developers. All rights reserved. | ||
// Use of this source code is governed by a BSD-style license | ||
// that can be found in the LICENSE file at the root of the | ||
// Mumble source tree or at <https://www.mumble.info/LICENSE>. | ||
|
||
#ifndef CROSSAUDIO_SRC_BACKENDS_WASAPI_DEVICE_HPP | ||
#define CROSSAUDIO_SRC_BACKENDS_WASAPI_DEVICE_HPP | ||
|
||
using Node = struct CrossAudio_Node; | ||
|
||
struct IMMDevice; | ||
|
||
namespace wasapi { | ||
bool populateNode(Node &node, IMMDevice &device, const wchar_t *id); | ||
|
||
char *utf16To8(const wchar_t *utf16); | ||
wchar_t *utf8To16(const char *utf8); | ||
} // namespace wasapi | ||
|
||
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
// Copyright 2024 The Mumble Developers. All rights reserved. | ||
// Use of this source code is governed by a BSD-style license | ||
// that can be found in the LICENSE file at the root of the | ||
// Mumble source tree or at <https://www.mumble.info/LICENSE>. | ||
|
||
#include "EventManager.hpp" | ||
|
||
#include "Device.hpp" | ||
|
||
#include "Node.h" | ||
|
||
using namespace wasapi; | ||
|
||
EventManager::EventManager(IMMDeviceEnumerator *enumerator, const Feedback &feedback) | ||
: m_feedback(feedback), m_ref(0), m_enumerator(enumerator) { | ||
m_enumerator->AddRef(); | ||
m_enumerator->RegisterEndpointNotificationCallback(this); | ||
} | ||
|
||
EventManager::~EventManager() { | ||
m_enumerator->UnregisterEndpointNotificationCallback(this); | ||
m_enumerator->Release(); | ||
} | ||
|
||
ULONG STDMETHODCALLTYPE EventManager::AddRef() { | ||
return InterlockedIncrement(&m_ref); | ||
} | ||
|
||
ULONG STDMETHODCALLTYPE EventManager::Release() { | ||
return InterlockedDecrement(&m_ref); | ||
} | ||
|
||
HRESULT STDMETHODCALLTYPE EventManager::QueryInterface(const IID &iid, void **iface) { | ||
if (iid == IID_IUnknown) { | ||
*iface = static_cast< IUnknown * >(this); | ||
AddRef(); | ||
} else if (iid == __uuidof(IMMNotificationClient)) { | ||
*iface = static_cast< IMMNotificationClient * >(this); | ||
AddRef(); | ||
} else { | ||
*iface = nullptr; | ||
return E_NOINTERFACE; | ||
} | ||
|
||
return S_OK; | ||
} | ||
|
||
HRESULT STDMETHODCALLTYPE EventManager::OnDeviceAdded(const wchar_t *id) { | ||
IMMDevice *device; | ||
if (m_enumerator->GetDevice(id, &device) != S_OK) { | ||
return S_OK; | ||
} | ||
|
||
Node *node = nodeNew(); | ||
if (populateNode(*node, *device, id)) { | ||
m_feedback.nodeAdded(node); | ||
} | ||
|
||
device->Release(); | ||
|
||
return S_OK; | ||
} | ||
|
||
HRESULT STDMETHODCALLTYPE EventManager::OnDeviceRemoved(const wchar_t *id) { | ||
IMMDevice *device; | ||
if (m_enumerator->GetDevice(id, &device) != S_OK) { | ||
return S_OK; | ||
} | ||
|
||
Node *node = nodeNew(); | ||
if (populateNode(*node, *device, id)) { | ||
m_feedback.nodeRemoved(node); | ||
} | ||
|
||
device->Release(); | ||
|
||
return S_OK; | ||
} | ||
|
||
HRESULT STDMETHODCALLTYPE EventManager::OnDeviceStateChanged(const wchar_t *id, DWORD state) { | ||
switch (state) { | ||
case DEVICE_STATE_ACTIVE: | ||
return OnDeviceAdded(id); | ||
case DEVICE_STATE_DISABLED: | ||
case DEVICE_STATE_NOTPRESENT: | ||
case DEVICE_STATE_UNPLUGGED: | ||
return OnDeviceRemoved(id); | ||
} | ||
|
||
return S_OK; | ||
} | ||
|
||
HRESULT STDMETHODCALLTYPE EventManager::OnDefaultDeviceChanged(EDataFlow, ERole, const wchar_t *) { | ||
return S_OK; | ||
} | ||
|
||
HRESULT STDMETHODCALLTYPE EventManager::OnPropertyValueChanged(const wchar_t *, const PROPERTYKEY) { | ||
return S_OK; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
// Copyright 2024 The Mumble Developers. All rights reserved. | ||
// Use of this source code is governed by a BSD-style license | ||
// that can be found in the LICENSE file at the root of the | ||
// Mumble source tree or at <https://www.mumble.info/LICENSE>. | ||
|
||
#ifndef CROSSAUDIO_SRC_BACKENDS_WASAPI_EVENTMANAGER_HPP | ||
#define CROSSAUDIO_SRC_BACKENDS_WASAPI_EVENTMANAGER_HPP | ||
|
||
#include <functional> | ||
|
||
#include <mmdeviceapi.h> | ||
|
||
using Node = struct CrossAudio_Node; | ||
|
||
namespace wasapi { | ||
class EventManager : public IMMNotificationClient { | ||
public: | ||
struct Feedback { | ||
std::function< void(Node *node) > nodeAdded; | ||
std::function< void(Node *node) > nodeRemoved; | ||
}; | ||
|
||
EventManager(IMMDeviceEnumerator *enumerator, const Feedback &feedback); | ||
virtual ~EventManager(); | ||
|
||
private: | ||
EventManager(const EventManager &) = delete; | ||
EventManager &operator=(const EventManager &) = delete; | ||
|
||
ULONG STDMETHODCALLTYPE AddRef() override; | ||
ULONG STDMETHODCALLTYPE Release() override; | ||
|
||
HRESULT STDMETHODCALLTYPE QueryInterface(const IID &iid, void **iface) override; | ||
HRESULT STDMETHODCALLTYPE OnDeviceAdded(const wchar_t *id) override; | ||
HRESULT STDMETHODCALLTYPE OnDeviceRemoved(const wchar_t *id) override; | ||
HRESULT STDMETHODCALLTYPE OnDeviceStateChanged(const wchar_t *id, DWORD state) override; | ||
HRESULT STDMETHODCALLTYPE OnDefaultDeviceChanged(EDataFlow flow, ERole role, const wchar_t *id) override; | ||
HRESULT STDMETHODCALLTYPE OnPropertyValueChanged(const wchar_t *id, const PROPERTYKEY key) override; | ||
|
||
Feedback m_feedback; | ||
LONG m_ref; | ||
IMMDeviceEnumerator *m_enumerator; | ||
}; | ||
} // namespace wasapi | ||
|
||
#endif |
Oops, something went wrong.