Fixed kdeconnectd deadlock on Windows caused by systemvolumeplugin
IAudioEndpointVolumeCallback::Release was called in callback functions of IMMNotificationClient (through the call to SystemvolumePlugin::sendSinkList), however https://docs.microsoft.com/en-us/windows/win32/api/mmdeviceapi/nn-mmdeviceapi-immnotificationclient points out that: *To avoid dead locks, the client should never call IMMDeviceEnumerator::RegisterEndpointNotificationCallback or IMMDeviceEnumerator::UnregisterEndpointNotificationCallback in its implementation of IMMNotificationClient methods. *The client should never release the final reference on an MMDevice API object during an event callback. So I moved that part of code to another thread so it will successfully run after callback functions work out and call only if endpoint needs to be released (device was removed), so it won't spawn new thread for every generated event
This commit is contained in:
parent
bb619baa18
commit
844d52f0a5
2 changed files with 124 additions and 81 deletions
|
@ -23,10 +23,73 @@ K_PLUGIN_CLASS_WITH_JSON(SystemvolumePlugin, "kdeconnect_systemvolume.json")
|
||||||
|
|
||||||
// Private classes of SystemvolumePlugin
|
// Private classes of SystemvolumePlugin
|
||||||
|
|
||||||
|
class SystemvolumePlugin::CAudioEndpointVolumeCallback : public IAudioEndpointVolumeCallback
|
||||||
|
{
|
||||||
|
LONG _cRef;
|
||||||
|
|
||||||
|
public:
|
||||||
|
CAudioEndpointVolumeCallback(SystemvolumePlugin &x, QString sinkName) : enclosing(x), name(std::move(sinkName)), _cRef(1) {}
|
||||||
|
~CAudioEndpointVolumeCallback(){};
|
||||||
|
|
||||||
|
// IUnknown methods -- AddRef, Release, and QueryInterface
|
||||||
|
|
||||||
|
ULONG STDMETHODCALLTYPE AddRef() override
|
||||||
|
{
|
||||||
|
return InterlockedIncrement(&_cRef);
|
||||||
|
}
|
||||||
|
|
||||||
|
ULONG STDMETHODCALLTYPE Release() override
|
||||||
|
{
|
||||||
|
ULONG ulRef = InterlockedDecrement(&_cRef);
|
||||||
|
if (ulRef == 0)
|
||||||
|
{
|
||||||
|
delete this;
|
||||||
|
}
|
||||||
|
return ulRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, VOID **ppvInterface) override
|
||||||
|
{
|
||||||
|
if (IID_IUnknown == riid)
|
||||||
|
{
|
||||||
|
AddRef();
|
||||||
|
*ppvInterface = (IUnknown *)this;
|
||||||
|
}
|
||||||
|
else if (__uuidof(IMMNotificationClient) == riid)
|
||||||
|
{
|
||||||
|
AddRef();
|
||||||
|
*ppvInterface = (IMMNotificationClient *)this;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
*ppvInterface = NULL;
|
||||||
|
return E_NOINTERFACE;
|
||||||
|
}
|
||||||
|
return S_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Callback method for endpoint-volume-change notifications.
|
||||||
|
|
||||||
|
HRESULT STDMETHODCALLTYPE OnNotify(PAUDIO_VOLUME_NOTIFICATION_DATA pNotify) override
|
||||||
|
{
|
||||||
|
NetworkPacket np(PACKET_TYPE_SYSTEMVOLUME);
|
||||||
|
np.set<int>(QStringLiteral("volume"), (int)(pNotify->fMasterVolume * 100));
|
||||||
|
np.set<bool>(QStringLiteral("muted"), pNotify->bMuted);
|
||||||
|
np.set<QString>(QStringLiteral("name"), name);
|
||||||
|
enclosing.sendPacket(np);
|
||||||
|
|
||||||
|
return S_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
SystemvolumePlugin &enclosing;
|
||||||
|
QString name;
|
||||||
|
};
|
||||||
|
|
||||||
class SystemvolumePlugin::CMMNotificationClient : public IMMNotificationClient
|
class SystemvolumePlugin::CMMNotificationClient : public IMMNotificationClient
|
||||||
{
|
{
|
||||||
|
|
||||||
public:
|
public:
|
||||||
CMMNotificationClient(SystemvolumePlugin &x) : enclosing(x), _cRef(1){};
|
CMMNotificationClient(SystemvolumePlugin &x) : enclosing(x), _cRef(1){};
|
||||||
|
|
||||||
~CMMNotificationClient(){};
|
~CMMNotificationClient(){};
|
||||||
|
@ -85,14 +148,59 @@ class SystemvolumePlugin::CMMNotificationClient : public IMMNotificationClient
|
||||||
return S_OK;
|
return S_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct RemovedDeviceThreadData {
|
||||||
|
|
||||||
|
QString qDeviceId;
|
||||||
|
SystemvolumePlugin* plugin;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
static DWORD WINAPI releaseRemovedDevice(_In_ LPVOID lpParameter) {
|
||||||
|
|
||||||
|
auto* data = static_cast<RemovedDeviceThreadData*>(lpParameter);
|
||||||
|
|
||||||
|
if (!data->plugin->sinkList.empty())
|
||||||
|
{
|
||||||
|
auto idToNameIterator = data->plugin->idToNameMap.find(data->qDeviceId);
|
||||||
|
if (idToNameIterator == data->plugin->idToNameMap.end()) return 0;
|
||||||
|
|
||||||
|
QString& sinkName = idToNameIterator.value();
|
||||||
|
|
||||||
|
auto sinkListIterator = data->plugin->sinkList.find(sinkName);
|
||||||
|
if (sinkListIterator == data->plugin->sinkList.end()) return 0;
|
||||||
|
|
||||||
|
auto& sink = sinkListIterator.value();
|
||||||
|
|
||||||
|
sink.first->UnregisterControlChangeNotify(sink.second);
|
||||||
|
sink.first->Release();
|
||||||
|
sink.second->Release();
|
||||||
|
|
||||||
|
data->plugin->idToNameMap.remove(data->qDeviceId);
|
||||||
|
data->plugin->sinkList.remove(sinkName);
|
||||||
|
}
|
||||||
|
|
||||||
|
data->plugin->sendSinkList();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
HRESULT STDMETHODCALLTYPE OnDeviceRemoved(LPCWSTR pwstrDeviceId) override
|
HRESULT STDMETHODCALLTYPE OnDeviceRemoved(LPCWSTR pwstrDeviceId) override
|
||||||
{
|
{
|
||||||
enclosing.sendSinkList();
|
static RemovedDeviceThreadData data {};
|
||||||
|
data.qDeviceId = QString::fromWCharArray(pwstrDeviceId);
|
||||||
|
data.plugin = &enclosing;
|
||||||
|
|
||||||
|
DWORD threadId;
|
||||||
|
HANDLE threadHandle = CreateThread(NULL, 0, releaseRemovedDevice, &data, 0, &threadId);
|
||||||
|
CloseHandle(threadHandle);
|
||||||
|
|
||||||
return S_OK;
|
return S_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
HRESULT STDMETHODCALLTYPE OnDeviceStateChanged(LPCWSTR pwstrDeviceId, DWORD dwNewState) override
|
HRESULT STDMETHODCALLTYPE OnDeviceStateChanged(LPCWSTR pwstrDeviceId, DWORD dwNewState) override
|
||||||
{
|
{
|
||||||
|
if (dwNewState == DEVICE_STATE_UNPLUGGED) return OnDeviceRemoved(pwstrDeviceId);
|
||||||
|
|
||||||
enclosing.sendSinkList();
|
enclosing.sendSinkList();
|
||||||
return S_OK;
|
return S_OK;
|
||||||
}
|
}
|
||||||
|
@ -103,74 +211,11 @@ class SystemvolumePlugin::CMMNotificationClient : public IMMNotificationClient
|
||||||
return S_OK;
|
return S_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
LONG _cRef;
|
LONG _cRef;
|
||||||
SystemvolumePlugin &enclosing;
|
SystemvolumePlugin &enclosing;
|
||||||
};
|
};
|
||||||
|
|
||||||
class SystemvolumePlugin::CAudioEndpointVolumeCallback : public IAudioEndpointVolumeCallback
|
|
||||||
{
|
|
||||||
LONG _cRef;
|
|
||||||
|
|
||||||
public:
|
|
||||||
CAudioEndpointVolumeCallback(SystemvolumePlugin &x, QString sinkName) : enclosing(x), name(sinkName), _cRef(1) {}
|
|
||||||
~CAudioEndpointVolumeCallback(){};
|
|
||||||
|
|
||||||
// IUnknown methods -- AddRef, Release, and QueryInterface
|
|
||||||
|
|
||||||
ULONG STDMETHODCALLTYPE AddRef() override
|
|
||||||
{
|
|
||||||
return InterlockedIncrement(&_cRef);
|
|
||||||
}
|
|
||||||
|
|
||||||
ULONG STDMETHODCALLTYPE Release() override
|
|
||||||
{
|
|
||||||
ULONG ulRef = InterlockedDecrement(&_cRef);
|
|
||||||
if (ulRef == 0)
|
|
||||||
{
|
|
||||||
delete this;
|
|
||||||
}
|
|
||||||
return ulRef;
|
|
||||||
}
|
|
||||||
|
|
||||||
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, VOID **ppvInterface) override
|
|
||||||
{
|
|
||||||
if (IID_IUnknown == riid)
|
|
||||||
{
|
|
||||||
AddRef();
|
|
||||||
*ppvInterface = (IUnknown *)this;
|
|
||||||
}
|
|
||||||
else if (__uuidof(IMMNotificationClient) == riid)
|
|
||||||
{
|
|
||||||
AddRef();
|
|
||||||
*ppvInterface = (IMMNotificationClient *)this;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
*ppvInterface = NULL;
|
|
||||||
return E_NOINTERFACE;
|
|
||||||
}
|
|
||||||
return S_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Callback method for endpoint-volume-change notifications.
|
|
||||||
|
|
||||||
HRESULT STDMETHODCALLTYPE OnNotify(PAUDIO_VOLUME_NOTIFICATION_DATA pNotify) override
|
|
||||||
{
|
|
||||||
NetworkPacket np(PACKET_TYPE_SYSTEMVOLUME);
|
|
||||||
np.set<int>(QStringLiteral("volume"), (int)(pNotify->fMasterVolume * 100));
|
|
||||||
np.set<bool>(QStringLiteral("muted"), pNotify->bMuted);
|
|
||||||
np.set<QString>(QStringLiteral("name"), name);
|
|
||||||
enclosing.sendPacket(np);
|
|
||||||
|
|
||||||
return S_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
SystemvolumePlugin &enclosing;
|
|
||||||
QString name;
|
|
||||||
};
|
|
||||||
|
|
||||||
SystemvolumePlugin::SystemvolumePlugin(QObject *parent, const QVariantList &args)
|
SystemvolumePlugin::SystemvolumePlugin(QObject *parent, const QVariantList &args)
|
||||||
: KdeConnectPlugin(parent, args),
|
: KdeConnectPlugin(parent, args),
|
||||||
sinkList()
|
sinkList()
|
||||||
|
@ -204,19 +249,8 @@ bool SystemvolumePlugin::sendSinkList()
|
||||||
QJsonDocument document;
|
QJsonDocument document;
|
||||||
QJsonArray array;
|
QJsonArray array;
|
||||||
|
|
||||||
HRESULT hr;
|
|
||||||
if (!sinkList.empty())
|
|
||||||
{
|
|
||||||
for (auto const &sink : sinkList)
|
|
||||||
{
|
|
||||||
sink.first->UnregisterControlChangeNotify(sink.second);
|
|
||||||
sink.first->Release();
|
|
||||||
sink.second->Release();
|
|
||||||
}
|
|
||||||
sinkList.clear();
|
|
||||||
}
|
|
||||||
IMMDeviceCollection *devices = nullptr;
|
IMMDeviceCollection *devices = nullptr;
|
||||||
hr = deviceEnumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &devices);
|
HRESULT hr = deviceEnumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &devices);
|
||||||
|
|
||||||
if (hr != S_OK)
|
if (hr != S_OK)
|
||||||
{
|
{
|
||||||
|
@ -234,6 +268,7 @@ bool SystemvolumePlugin::sendSinkList()
|
||||||
PROPVARIANT deviceProperty;
|
PROPVARIANT deviceProperty;
|
||||||
QString name;
|
QString name;
|
||||||
QString desc;
|
QString desc;
|
||||||
|
LPWSTR deviceId;
|
||||||
float volume;
|
float volume;
|
||||||
BOOL muted;
|
BOOL muted;
|
||||||
|
|
||||||
|
@ -267,6 +302,8 @@ bool SystemvolumePlugin::sendSinkList()
|
||||||
device->Release();
|
device->Release();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
device->GetId(&deviceId);
|
||||||
endpoint->GetMasterVolumeLevelScalar(&volume);
|
endpoint->GetMasterVolumeLevelScalar(&volume);
|
||||||
endpoint->GetMute(&muted);
|
endpoint->GetMute(&muted);
|
||||||
|
|
||||||
|
@ -275,9 +312,14 @@ bool SystemvolumePlugin::sendSinkList()
|
||||||
sinkObject.insert(QStringLiteral("maxVolume"), (qint64)100);
|
sinkObject.insert(QStringLiteral("maxVolume"), (qint64)100);
|
||||||
|
|
||||||
// Register Callback
|
// Register Callback
|
||||||
callback = new CAudioEndpointVolumeCallback(*this, name);
|
QString qDeviceId = QString::fromWCharArray(deviceId);
|
||||||
sinkList[name] = qMakePair(endpoint, callback);
|
if (!sinkList.contains(qDeviceId)) {
|
||||||
endpoint->RegisterControlChangeNotify(callback);
|
callback = new CAudioEndpointVolumeCallback(*this, name);
|
||||||
|
endpoint->RegisterControlChangeNotify(callback);
|
||||||
|
sinkList[name] = qMakePair(endpoint, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
idToNameMap[qDeviceId] = name;
|
||||||
|
|
||||||
device->Release();
|
device->Release();
|
||||||
array.append(sinkObject);
|
array.append(sinkObject);
|
||||||
|
|
|
@ -41,6 +41,7 @@ class Q_DECL_EXPORT SystemvolumePlugin : public KdeConnectPlugin
|
||||||
IMMDeviceEnumerator* deviceEnumerator;
|
IMMDeviceEnumerator* deviceEnumerator;
|
||||||
CMMNotificationClient* deviceCallback;
|
CMMNotificationClient* deviceCallback;
|
||||||
QMap<QString, QPair<IAudioEndpointVolume *, CAudioEndpointVolumeCallback *>> sinkList;
|
QMap<QString, QPair<IAudioEndpointVolume *, CAudioEndpointVolumeCallback *>> sinkList;
|
||||||
|
QMap<QString, QString> idToNameMap;
|
||||||
|
|
||||||
bool sendSinkList();
|
bool sendSinkList();
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue