1 // Copyright 2005-2019 The Mumble Developers. All rights reserved.
2 // Use of this source code is governed by a BSD-style license
3 // that can be found in the LICENSE file at the root of the
4 // Mumble source tree or at <https://www.mumble.info/LICENSE>.
5 
6 #include "mumble_pch.hpp"
7 
8 #include "MainWindow.h"
9 
10 #include "WASAPINotificationClient.h"
11 
12 #include <QtCore/QMutexLocker>
13 #include <boost/thread/once.hpp>
14 
15 // We define a global macro called 'g'. This can lead to issues when included code uses 'g' as a type or parameter name (like protobuf 3.7 does). As such, for now, we have to make this our last include.
16 #include "Global.h"
17 
OnDefaultDeviceChanged(EDataFlow flow,ERole role,LPCWSTR pwstrDefaultDevice)18 HRESULT STDMETHODCALLTYPE WASAPINotificationClient::OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR pwstrDefaultDevice) {
19 	const QString device = QString::fromWCharArray(pwstrDefaultDevice);
20 
21 	qWarning()	<< "WASAPINotificationClient: Default device changed flow=" << flow
22 				<< "role=" << role
23 				<< "device" << device;
24 
25 	QMutexLocker lock(&listsMutex);
26 	if (!usedDefaultDevices.empty() && role == eCommunications) {
27 		restartAudio();
28 	}
29 	return S_OK;
30 }
31 
OnPropertyValueChanged(LPCWSTR pwstrDeviceId,const PROPERTYKEY key)32 HRESULT STDMETHODCALLTYPE WASAPINotificationClient::OnPropertyValueChanged(LPCWSTR pwstrDeviceId, const PROPERTYKEY key) {
33 	const QString device = QString::fromWCharArray(pwstrDeviceId);
34 
35 	const bool formatChanged = (key == PKEY_AudioEngine_DeviceFormat);
36 	const bool channelConfigChanged = (key == PKEY_AudioEndpoint_PhysicalSpeakers);
37 
38 	QMutexLocker lock(&listsMutex);
39 	if ((formatChanged || channelConfigChanged) && usedDevices.contains(device)) {
40 		qWarning()	<<"WASAPINotificationClient: Property changed device=" << device
41 					<< "formatChanged=" << formatChanged
42 					<< "channelConfigChanged=" << channelConfigChanged;
43 
44 		restartAudio();
45 	}
46 	return S_OK;
47 }
48 
OnDeviceAdded(LPCWSTR pwstrDeviceId)49 HRESULT STDMETHODCALLTYPE WASAPINotificationClient::OnDeviceAdded(LPCWSTR pwstrDeviceId) {
50 	const QString device = QString::fromWCharArray(pwstrDeviceId);
51 	qWarning() << "WASAPINotificationClient: Device added=" << device;
52 	return S_OK;
53 }
OnDeviceRemoved(LPCWSTR pwstrDeviceId)54 HRESULT STDMETHODCALLTYPE WASAPINotificationClient::OnDeviceRemoved(LPCWSTR pwstrDeviceId) {
55 	const QString device = QString::fromWCharArray(pwstrDeviceId);
56 	qWarning() << "WASAPINotificationClient: Device removed=" << device;
57 	return S_OK;
58 }
59 
OnDeviceStateChanged(LPCWSTR pwstrDeviceId,DWORD dwNewState)60 HRESULT STDMETHODCALLTYPE WASAPINotificationClient::OnDeviceStateChanged(LPCWSTR pwstrDeviceId, DWORD dwNewState) {
61 	const QString device = QString::fromWCharArray(pwstrDeviceId);
62 
63 	qWarning()	<< "WASAPINotificationClient: Device state changed newState=" << dwNewState
64 				<< "device=" << device;
65 
66 	return S_OK;
67 }
68 
QueryInterface(REFIID riid,VOID ** ppvInterface)69 HRESULT STDMETHODCALLTYPE WASAPINotificationClient::QueryInterface(REFIID riid, VOID **ppvInterface) {
70 	if (IID_IUnknown == riid)
71 	{
72 		*ppvInterface = (IUnknown*)this;
73 		AddRef();
74 	}
75 	else if (__uuidof(IMMNotificationClient) == riid)
76 	{
77 		*ppvInterface = (IMMNotificationClient*)this;
78 		AddRef();
79 	}
80 	else
81 	{
82 		*ppvInterface = NULL;
83 		return E_NOINTERFACE;
84 	}
85 	return S_OK;
86 }
87 
AddRef()88 ULONG STDMETHODCALLTYPE WASAPINotificationClient::AddRef() {
89 	return InterlockedIncrement(&_cRef);
90 }
91 
Release()92 ULONG STDMETHODCALLTYPE WASAPINotificationClient::Release() {
93 	// We hold a ref to ourselves all the time (static singleton) so no
94 	// need to clean ourselves up or anything.
95 	ULONG ulRef = InterlockedDecrement(&_cRef);
96 	Q_ASSERT(ulRef > 0);
97 	return ulRef;
98 }
99 
enlistDefaultDeviceAsUsed(LPCWSTR pwstrDefaultDevice)100 void WASAPINotificationClient::enlistDefaultDeviceAsUsed(LPCWSTR pwstrDefaultDevice) {
101 	const QString device = QString::fromWCharArray(pwstrDefaultDevice);
102 	QMutexLocker lock(&listsMutex);
103 	if (!usedDefaultDevices.contains(device)) {
104 		usedDefaultDevices.append(device);
105 		_enlistDeviceAsUsed(device);
106 	}
107 }
108 
enlistDeviceAsUsed(LPCWSTR pwstrDevice)109 void WASAPINotificationClient::enlistDeviceAsUsed(LPCWSTR pwstrDevice) {
110 	const QString device = QString::fromWCharArray(pwstrDevice);
111 	QMutexLocker lock(&listsMutex);
112 	_enlistDeviceAsUsed(device);
113 }
114 
_enlistDeviceAsUsed(const QString & device)115 void WASAPINotificationClient::_enlistDeviceAsUsed(const QString& device)
116 {
117 	if (!usedDevices.contains(device)) {
118 		usedDevices.append(device);
119 	}
120 }
121 
enlistDeviceAsUsed(const QString & device)122 void WASAPINotificationClient::enlistDeviceAsUsed(const QString& device) {
123 	QMutexLocker lock(&listsMutex);
124 	_enlistDeviceAsUsed(device);
125 }
126 
unlistDevice(LPCWSTR pwstrDevice)127 void WASAPINotificationClient::unlistDevice(LPCWSTR pwstrDevice) {
128 	const QString device = QString::fromWCharArray(pwstrDevice);
129 	QMutexLocker lock(&listsMutex);
130 	usedDevices.removeOne(device);
131 	usedDefaultDevices.removeOne(device);
132 }
133 
clearUsedDefaultDeviceList()134 void WASAPINotificationClient::clearUsedDefaultDeviceList() {
135 	QMutexLocker lock(&listsMutex);
136 	usedDefaultDevices.clear();
137 }
138 
_clearUsedDeviceLists()139 void WASAPINotificationClient::_clearUsedDeviceLists()
140 {
141 	usedDefaultDevices.clear();
142 	usedDevices.clear();
143 }
144 
clearUsedDeviceLists()145 void WASAPINotificationClient::clearUsedDeviceLists() {
146 	QMutexLocker lock(&listsMutex);
147 	_clearUsedDeviceLists();
148 }
149 
doGetOnce()150 void WASAPINotificationClient::doGetOnce() {
151 	(void)WASAPINotificationClient::doGet();
152 }
153 
doGet()154 WASAPINotificationClient& WASAPINotificationClient::doGet() {
155 	static WASAPINotificationClient instance;
156 	return instance;
157 }
158 
159 static boost::once_flag notification_client_init_once = BOOST_ONCE_INIT;
160 
get()161 WASAPINotificationClient& WASAPINotificationClient::get() {
162 	// Hacky way of making sure we get a thread-safe yet lazy initialization of the static.
163 	boost::call_once(&WASAPINotificationClient::doGetOnce, notification_client_init_once);
164 	return doGet();
165 }
166 
WASAPINotificationClient()167 WASAPINotificationClient::WASAPINotificationClient() : QObject(), pEnumerator(0), listsMutex() {
168 	AddRef(); // Static singleton, always has a self-reference
169 
170 	HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_ALL, __uuidof(IMMDeviceEnumerator), reinterpret_cast<void **>(&pEnumerator));
171 	if (!pEnumerator || FAILED(hr)) {
172 		if (pEnumerator) {
173 			pEnumerator->Release();
174 			pEnumerator = 0;
175 		}
176 		qWarning() << "WASAPINotificationClient: Failed to create enumerator, will not receive notifications";
177 		return;
178 	}
179 
180 	g.mw->connect(this, SIGNAL(doResetAudio()), SLOT(onResetAudio()), Qt::QueuedConnection);
181 
182 	pEnumerator->RegisterEndpointNotificationCallback(this);
183 }
184 
~WASAPINotificationClient()185 WASAPINotificationClient::~WASAPINotificationClient() {
186 	if (pEnumerator)
187 		pEnumerator->Release();
188 }
189 
restartAudio()190 void WASAPINotificationClient::restartAudio() {
191 	qWarning("WASAPINotificationClient: Triggering audio reset");
192 	_clearUsedDeviceLists();
193 	emit doResetAudio();
194 }
195