diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt
index f0721cce4..a7a477952 100644
--- a/plugins/CMakeLists.txt
+++ b/plugins/CMakeLists.txt
@@ -40,7 +40,7 @@ if(SAILFISHOS OR EXPERIMENTALAPP_ENABLED)
add_subdirectory(remotesystemvolume)
endif()
-if(KF5PulseAudioQt_FOUND)
+if(KF5PulseAudioQt_FOUND OR WIN32)
add_subdirectory(systemvolume)
endif()
diff --git a/plugins/systemvolume/CMakeLists.txt b/plugins/systemvolume/CMakeLists.txt
index 50a1d7cc5..176243772 100644
--- a/plugins/systemvolume/CMakeLists.txt
+++ b/plugins/systemvolume/CMakeLists.txt
@@ -1,11 +1,25 @@
-set(kdeconnect_systemvolume_SRCS
- systemvolumeplugin.cpp
-)
+if(WIN32)
+ set(kdeconnect_systemvolume_SRCS
+ systemvolumeplugin-win.cpp
+ )
+else()
+ set(kdeconnect_systemvolume_SRCS
+ systemvolumeplugin-pulse.cpp
+ )
+endif()
kdeconnect_add_plugin(kdeconnect_systemvolume JSON kdeconnect_systemvolume.json SOURCES ${kdeconnect_systemvolume_SRCS})
-target_link_libraries(kdeconnect_systemvolume
- kdeconnectcore
- Qt5::Core
- KF5::PulseAudioQt
-)
+if(WIN32)
+ target_link_libraries(kdeconnect_systemvolume
+ kdeconnectcore
+ Qt5::Core
+ ole32
+ )
+else()
+ target_link_libraries(kdeconnect_systemvolume
+ kdeconnectcore
+ Qt5::Core
+ KF5::PulseAudioQt
+ )
+endif()
diff --git a/plugins/systemvolume/systemvolumeplugin.cpp b/plugins/systemvolume/systemvolumeplugin-pulse.cpp
similarity index 98%
rename from plugins/systemvolume/systemvolumeplugin.cpp
rename to plugins/systemvolume/systemvolumeplugin-pulse.cpp
index 1261986e3..3b37d7761 100644
--- a/plugins/systemvolume/systemvolumeplugin.cpp
+++ b/plugins/systemvolume/systemvolumeplugin-pulse.cpp
@@ -18,7 +18,7 @@
* along with this program. If not, see .
*/
-#include "systemvolumeplugin.h"
+#include "systemvolumeplugin-pulse.h"
#include
@@ -123,5 +123,5 @@ void SystemvolumePlugin::connected()
}
}
-#include "systemvolumeplugin.moc"
+#include "systemvolumeplugin-pulse.moc"
diff --git a/plugins/systemvolume/systemvolumeplugin.h b/plugins/systemvolume/systemvolumeplugin-pulse.h
similarity index 95%
rename from plugins/systemvolume/systemvolumeplugin.h
rename to plugins/systemvolume/systemvolumeplugin-pulse.h
index f1f34455c..5843f1292 100644
--- a/plugins/systemvolume/systemvolumeplugin.h
+++ b/plugins/systemvolume/systemvolumeplugin-pulse.h
@@ -18,8 +18,8 @@
* along with this program. If not, see .
*/
-#ifndef SYSTEMVOLUMEPLUGIN_H
-#define SYSTEMVOLUMEPLUGIN_H
+#ifndef SYSTEMVOLUMEPLUGINPULSE_H
+#define SYSTEMVOLUMEPLUGINPULSE_H
#include
#include
diff --git a/plugins/systemvolume/systemvolumeplugin-win.cpp b/plugins/systemvolume/systemvolumeplugin-win.cpp
new file mode 100644
index 000000000..7b3f1d5a4
--- /dev/null
+++ b/plugins/systemvolume/systemvolumeplugin-win.cpp
@@ -0,0 +1,325 @@
+#include "systemvolumeplugin-win.h"
+#include
+
+#include
+
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+K_PLUGIN_FACTORY_WITH_JSON(KdeConnectPluginFactory, "kdeconnect_systemvolume.json", registerPlugin();)
+
+Q_LOGGING_CATEGORY(KDECONNECT_PLUGIN_SYSTEMVOLUME, "kdeconnect.plugin.systemvolume")
+
+// Private classes of SystemvolumePlugin
+
+class SystemvolumePlugin::CMMNotificationClient : public IMMNotificationClient
+{
+
+ public:
+ CMMNotificationClient(SystemvolumePlugin &x) : enclosing(x), _cRef(1){};
+
+ ~CMMNotificationClient(){};
+
+ // 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 methods for device-event notifications.
+
+ HRESULT STDMETHODCALLTYPE OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR pwstrDeviceId) override
+ {
+ if (flow == eRender)
+ {
+ enclosing.sendSinkList();
+ }
+ return S_OK;
+ }
+
+ HRESULT STDMETHODCALLTYPE OnDeviceAdded(LPCWSTR pwstrDeviceId) override
+ {
+ enclosing.sendSinkList();
+ return S_OK;
+ }
+
+ HRESULT STDMETHODCALLTYPE OnDeviceRemoved(LPCWSTR pwstrDeviceId) override
+ {
+ enclosing.sendSinkList();
+ return S_OK;
+ }
+
+ HRESULT STDMETHODCALLTYPE OnDeviceStateChanged(LPCWSTR pwstrDeviceId, DWORD dwNewState) override
+ {
+ enclosing.sendSinkList();
+ return S_OK;
+ }
+
+ HRESULT STDMETHODCALLTYPE OnPropertyValueChanged(LPCWSTR pwstrDeviceId, const PROPERTYKEY key) override
+ {
+ enclosing.sendSinkList();
+ return S_OK;
+ }
+
+ private:
+ LONG _cRef;
+ 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(QStringLiteral("volume"), (int)(pNotify->fMasterVolume * 100));
+ np.set(QStringLiteral("muted"), pNotify->bMuted);
+ np.set(QStringLiteral("name"), name);
+ enclosing.sendPacket(np);
+
+ return S_OK;
+ }
+
+ private:
+ SystemvolumePlugin &enclosing;
+ QString name;
+};
+
+SystemvolumePlugin::SystemvolumePlugin(QObject *parent, const QVariantList &args)
+ : KdeConnectPlugin(parent, args),
+ sinkList()
+{
+ CoInitialize(nullptr);
+ deviceEnumerator = nullptr;
+ HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_INPROC_SERVER, __uuidof(IMMDeviceEnumerator), (LPVOID *)&(deviceEnumerator));
+ valid = (hr == S_OK);
+ if (!valid)
+ {
+ qWarning("Initialization failed: Failed to create MMDeviceEnumerator");
+ qWarning("Error Code: %lx", hr);
+ }
+}
+
+SystemvolumePlugin::~SystemvolumePlugin()
+{
+ if (valid)
+ {
+ deviceEnumerator->UnregisterEndpointNotificationCallback(deviceCallback);
+ deviceEnumerator->Release();
+ deviceEnumerator = nullptr;
+ }
+}
+
+bool SystemvolumePlugin::sendSinkList()
+{
+ if (!valid)
+ return false;
+
+ QJsonDocument document;
+ 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;
+ hr = deviceEnumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &devices);
+
+ if (hr != S_OK)
+ {
+ qWarning("Failed to Enumumerate AudioEndpoints");
+ qWarning("Error Code: %lx", hr);
+ return false;
+ }
+ unsigned int deviceCount;
+ devices->GetCount(&deviceCount);
+ for (unsigned int i = 0; i < deviceCount; i++)
+ {
+ IMMDevice *device = nullptr;
+
+ IPropertyStore *deviceProperties = nullptr;
+ PROPVARIANT deviceProperty;
+ QString name;
+ QString desc;
+ float volume;
+ BOOL muted;
+
+ IAudioEndpointVolume *endpoint = nullptr;
+ CAudioEndpointVolumeCallback *callback;
+
+ // Get Properties
+ devices->Item(i, &device);
+ device->OpenPropertyStore(STGM_READ, &deviceProperties);
+
+ deviceProperties->GetValue(PKEY_Device_FriendlyName, &deviceProperty);
+ name = QString::fromWCharArray(deviceProperty.pwszVal);
+ //PropVariantClear(&deviceProperty);
+
+ deviceProperties->GetValue(PKEY_Device_DeviceDesc, &deviceProperty);
+ desc = QString::fromWCharArray(deviceProperty.pwszVal);
+ //PropVariantClear(&deviceProperty);
+
+ QJsonObject sinkObject;
+ sinkObject.insert("name", name);
+ sinkObject.insert("description", desc);
+
+ hr = device->Activate(__uuidof(IAudioEndpointVolume), CLSCTX_ALL, NULL, (void **)&endpoint);
+ if (hr != S_OK)
+ {
+ qWarning() << "Failed to create IAudioEndpointVolume for device:" << name;
+ qWarning("Error Code: %lx", hr);
+
+ device->Release();
+ continue;
+ }
+ endpoint->GetMasterVolumeLevelScalar(&volume);
+ endpoint->GetMute(&muted);
+
+ sinkObject.insert("muted", (bool)muted);
+ sinkObject.insert("volume", (qint64)(volume * 100));
+ sinkObject.insert("maxVolume", (qint64)100);
+
+ // Register Callback
+ callback = new CAudioEndpointVolumeCallback(*this, name);
+ sinkList[name] = qMakePair(endpoint, callback);
+ endpoint->RegisterControlChangeNotify(callback);
+
+ device->Release();
+ array.append(sinkObject);
+ }
+ devices->Release();
+
+ document.setArray(array);
+
+ NetworkPacket np(PACKET_TYPE_SYSTEMVOLUME);
+ np.set(QStringLiteral("sinkList"), document);
+ sendPacket(np);
+ return true;
+}
+
+void SystemvolumePlugin::connected()
+{
+ if (!valid)
+ return;
+
+ deviceCallback = new CMMNotificationClient(*this);
+ deviceEnumerator->RegisterEndpointNotificationCallback(deviceCallback);
+ sendSinkList();
+}
+
+bool SystemvolumePlugin::receivePacket(const NetworkPacket &np)
+{
+ if (!valid)
+ return false;
+
+ if (np.has(QStringLiteral("requestSinks")))
+ {
+ return sendSinkList();
+ }
+ else
+ {
+ QString name = np.get(QStringLiteral("name"));
+
+ if (sinkList.contains(name))
+ {
+ if (np.has(QStringLiteral("volume")))
+ {
+ sinkList[name].first->SetMasterVolumeLevelScalar((float)np.get(QStringLiteral("volume")) / 100, NULL);
+ }
+ if (np.has(QStringLiteral("muted")))
+ {
+ sinkList[name].first->SetMute(np.get(QStringLiteral("muted")), NULL);
+ }
+ }
+ }
+ return true;
+}
+
+#include "systemvolumeplugin-win.moc"
\ No newline at end of file
diff --git a/plugins/systemvolume/systemvolumeplugin-win.h b/plugins/systemvolume/systemvolumeplugin-win.h
new file mode 100644
index 000000000..6c609336e
--- /dev/null
+++ b/plugins/systemvolume/systemvolumeplugin-win.h
@@ -0,0 +1,38 @@
+#ifndef SYSTEMVOLUMEPLUGINWIN_H
+#define SYSTEMVOLUMEPLUGINWIN_H
+
+#include
+#include
+
+#include
+
+#include
+#include
+#include
+
+#define PACKET_TYPE_SYSTEMVOLUME QStringLiteral("kdeconnect.systemvolume")
+#define PACKET_TYPE_SYSTEMVOLUME_REQUEST QStringLiteral("kdeconnect.systemvolume.request")
+
+class Q_DECL_EXPORT SystemvolumePlugin : public KdeConnectPlugin
+{
+ Q_OBJECT
+
+ public:
+ explicit SystemvolumePlugin(QObject *parent, const QVariantList &args);
+ ~SystemvolumePlugin();
+ bool receivePacket(const NetworkPacket& np) override;
+ void connected() override;
+
+ private:
+ class CMMNotificationClient;
+ class CAudioEndpointVolumeCallback;
+
+ bool valid;
+ IMMDeviceEnumerator* deviceEnumerator;
+ CMMNotificationClient* deviceCallback;
+ QMap> sinkList;
+
+ bool sendSinkList();
+};
+
+#endif // SYSTEMVOLUMEPLUGINWIN_H