diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index f0721cce..a7a47795 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -1,49 +1,49 @@ include_directories(${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR}/core) add_definitions(-DTRANSLATION_DOMAIN=\"kdeconnect-plugins\") install(FILES kdeconnect_plugin.desktop DESTINATION ${SERVICETYPES_INSTALL_DIR}) add_subdirectory(ping) add_subdirectory(battery) add_subdirectory(sendnotifications) if (NOT WIN32) add_subdirectory(mpriscontrol) endif() if(NOT SAILFISHOS) add_subdirectory(clipboard) add_subdirectory(contacts) add_subdirectory(share) add_subdirectory(remotekeyboard) add_subdirectory(notifications) add_subdirectory(findmyphone) add_subdirectory(telephony) add_subdirectory(mousepad) add_subdirectory(sms) add_subdirectory(runcommand) if(NOT WIN32) add_subdirectory(pausemusic) add_subdirectory(screensaver-inhibit) add_subdirectory(sftp) endif() if(Phonon4Qt5_FOUND) add_subdirectory(findthisdevice) endif() endif() if(SAILFISHOS OR EXPERIMENTALAPP_ENABLED) add_subdirectory(remotecommands) add_subdirectory(mprisremote) add_subdirectory(remotecontrol) add_subdirectory(lockdevice) add_subdirectory(remotesystemvolume) endif() -if(KF5PulseAudioQt_FOUND) +if(KF5PulseAudioQt_FOUND OR WIN32) add_subdirectory(systemvolume) endif() #FIXME: If we split notifications in several files, they won't appear in the same group in the Notifications KCM install(FILES kdeconnect.notifyrc DESTINATION ${KNOTIFYRC_INSTALL_DIR}) diff --git a/plugins/systemvolume/CMakeLists.txt b/plugins/systemvolume/CMakeLists.txt index 50a1d7cc..17624377 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 1261986e..3b37d776 100644 --- a/plugins/systemvolume/systemvolumeplugin.cpp +++ b/plugins/systemvolume/systemvolumeplugin-pulse.cpp @@ -1,127 +1,127 @@ /** * Copyright 2017 Nicolas Fella * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#include "systemvolumeplugin.h" +#include "systemvolumeplugin-pulse.h" #include #include #include #include #include #include #include #include #include #include K_PLUGIN_FACTORY_WITH_JSON( KdeConnectPluginFactory, "kdeconnect_systemvolume.json", registerPlugin< SystemvolumePlugin >(); ) Q_LOGGING_CATEGORY(KDECONNECT_PLUGIN_SYSTEMVOLUME, "kdeconnect.plugin.systemvolume") SystemvolumePlugin::SystemvolumePlugin(QObject* parent, const QVariantList& args) : KdeConnectPlugin(parent, args) , sinksMap() {} bool SystemvolumePlugin::receivePacket(const NetworkPacket& np) { if (!PulseAudioQt::Context::instance()->isValid()) return false; if (np.has(QStringLiteral("requestSinks"))) { sendSinkList(); } else { QString name = np.get(QStringLiteral("name")); if (sinksMap.contains(name)) { if (np.has(QStringLiteral("volume"))) { sinksMap[name]->setVolume(np.get(QStringLiteral("volume"))); } if (np.has(QStringLiteral("muted"))) { sinksMap[name]->setMuted(np.get(QStringLiteral("muted"))); } } } return true; } void SystemvolumePlugin::sendSinkList() { QJsonDocument document; QJsonArray array; sinksMap.clear(); for (PulseAudioQt::Sink* sink : PulseAudioQt::Context::instance()->sinks()) { sinksMap.insert(sink->name(), sink); connect(sink, &PulseAudioQt::Sink::volumeChanged, this, [this, sink] { NetworkPacket np(PACKET_TYPE_SYSTEMVOLUME); np.set(QStringLiteral("volume"), sink->volume()); np.set(QStringLiteral("name"), sink->name()); sendPacket(np); }); connect(sink, &PulseAudioQt::Sink::mutedChanged, this, [this, sink] { NetworkPacket np(PACKET_TYPE_SYSTEMVOLUME); np.set(QStringLiteral("muted"), sink->isMuted()); np.set(QStringLiteral("name"), sink->name()); sendPacket(np); }); QJsonObject sinkObject; sinkObject.insert("name", sink->name()); sinkObject.insert("description", sink->description()); sinkObject.insert("muted", sink->isMuted()); sinkObject.insert("volume", sink->volume()); sinkObject.insert("maxVolume", PulseAudioQt::normalVolume()); array.append(sinkObject); } document.setArray(array); NetworkPacket np(PACKET_TYPE_SYSTEMVOLUME); np.set(QStringLiteral("sinkList"), document); sendPacket(np); } void SystemvolumePlugin::connected() { connect(PulseAudioQt::Context::instance(), &PulseAudioQt::Context::sinkAdded, this, [this] { sendSinkList(); }); connect(PulseAudioQt::Context::instance(), &PulseAudioQt::Context::sinkRemoved, this, [this] { sendSinkList(); }); for (PulseAudioQt::Sink* sink : PulseAudioQt::Context::instance()->sinks()) { sinksMap.insert(sink->name(), sink); } } -#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 f1f34455..5843f129 100644 --- a/plugins/systemvolume/systemvolumeplugin.h +++ b/plugins/systemvolume/systemvolumeplugin-pulse.h @@ -1,51 +1,51 @@ /** * Copyright 2017 Nicolas Fella * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#ifndef SYSTEMVOLUMEPLUGIN_H -#define SYSTEMVOLUMEPLUGIN_H +#ifndef SYSTEMVOLUMEPLUGINPULSE_H +#define SYSTEMVOLUMEPLUGINPULSE_H #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); bool receivePacket(const NetworkPacket& np) override; void connected() override; private: void sendSinkList(); QMap sinksMap; }; #endif diff --git a/plugins/systemvolume/systemvolumeplugin-win.cpp b/plugins/systemvolume/systemvolumeplugin-win.cpp new file mode 100644 index 00000000..7b3f1d5a --- /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 00000000..6c609336 --- /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