1d36164230
This automatizes the generation of logging categories so a kdeconnect-kde.categories is generated and installed to /usr/share/qlogging-categories5/ so kdebugsettings can use it. Also, sets the default logging level to Warning. So now the logs of users won't be filled with debug messages but they can modify the configuration easily with kdebugsettings.
444 lines
15 KiB
C++
444 lines
15 KiB
C++
/**
|
|
* Copyright 2019 Weixuan XIAO <veyx.shaw@gmail.com>
|
|
*
|
|
* 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 <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "systemvolumeplugin-macos.h"
|
|
|
|
#include <KPluginFactory>
|
|
|
|
#include <QDebug>
|
|
#include <QJsonArray>
|
|
#include <QJsonDocument>
|
|
#include <QJsonObject>
|
|
#include "plugin_systemvolume_debug.h"
|
|
|
|
K_PLUGIN_FACTORY_WITH_JSON( KdeConnectPluginFactory, "kdeconnect_systemvolume.json", registerPlugin< SystemvolumePlugin >(); )
|
|
|
|
|
|
class MacOSCoreAudioDevice
|
|
{
|
|
private:
|
|
AudioDeviceID m_deviceId;
|
|
QString m_description;
|
|
bool m_isStereo;
|
|
|
|
friend class SystemvolumePlugin;
|
|
public:
|
|
MacOSCoreAudioDevice(AudioDeviceID);
|
|
~MacOSCoreAudioDevice();
|
|
|
|
void setVolume(float volume);
|
|
float volume();
|
|
void setMuted(bool muted);
|
|
bool isMuted();
|
|
|
|
void updateType();
|
|
};
|
|
|
|
static const AudioObjectPropertyAddress kAudioHardwarePropertyAddress = {
|
|
kAudioHardwarePropertyDevices,
|
|
kAudioObjectPropertyScopeGlobal,
|
|
kAudioObjectPropertyElementMaster
|
|
};
|
|
|
|
static const AudioObjectPropertyAddress kAudioStreamPropertyAddress = {
|
|
kAudioDevicePropertyStreams,
|
|
kAudioDevicePropertyScopeOutput,
|
|
kAudioObjectPropertyElementMaster
|
|
};
|
|
|
|
static const AudioObjectPropertyAddress kAudioMasterVolumePropertyAddress = {
|
|
kAudioDevicePropertyVolumeScalar,
|
|
kAudioDevicePropertyScopeOutput,
|
|
kAudioObjectPropertyElementMaster
|
|
};
|
|
|
|
static const AudioObjectPropertyAddress kAudioLeftVolumePropertyAddress = {
|
|
kAudioDevicePropertyVolumeScalar,
|
|
kAudioDevicePropertyScopeOutput,
|
|
1
|
|
};
|
|
|
|
static const AudioObjectPropertyAddress kAudioRightVolumePropertyAddress = {
|
|
kAudioDevicePropertyVolumeScalar,
|
|
kAudioDevicePropertyScopeOutput,
|
|
2
|
|
};
|
|
|
|
static const AudioObjectPropertyAddress kAudioMasterMutedPropertyAddress = {
|
|
kAudioDevicePropertyMute,
|
|
kAudioDevicePropertyScopeOutput,
|
|
kAudioObjectPropertyElementMaster
|
|
};
|
|
|
|
static const AudioObjectPropertyAddress kAudioMasterDataSourcePropertyAddress = {
|
|
kAudioDevicePropertyDataSource,
|
|
kAudioDevicePropertyScopeOutput,
|
|
kAudioObjectPropertyElementMaster
|
|
};
|
|
|
|
OSStatus onVolumeChanged(AudioObjectID object, UInt32 numAddresses, const AudioObjectPropertyAddress addresses[], void *context)
|
|
{
|
|
Q_UNUSED(object);
|
|
Q_UNUSED(addresses);
|
|
Q_UNUSED(numAddresses);
|
|
|
|
SystemvolumePlugin *plugin = (SystemvolumePlugin*)context;
|
|
plugin->updateDeviceVolume(object);
|
|
return noErr;
|
|
}
|
|
|
|
OSStatus onMutedChanged(AudioObjectID object, UInt32 numAddresses, const AudioObjectPropertyAddress addresses[], void *context)
|
|
{
|
|
Q_UNUSED(object);
|
|
Q_UNUSED(addresses);
|
|
Q_UNUSED(numAddresses);
|
|
|
|
SystemvolumePlugin *plugin = (SystemvolumePlugin*)context;
|
|
plugin->updateDeviceMuted(object);
|
|
return noErr;
|
|
}
|
|
|
|
OSStatus onOutputSourceChanged(AudioObjectID object, UInt32 numAddresses, const AudioObjectPropertyAddress addresses[], void *context)
|
|
{
|
|
Q_UNUSED(object);
|
|
Q_UNUSED(addresses);
|
|
Q_UNUSED(numAddresses);
|
|
|
|
SystemvolumePlugin *plugin = (SystemvolumePlugin*)context;
|
|
plugin->sendSinkList();
|
|
return noErr;
|
|
}
|
|
|
|
UInt32 getDeviceSourceId(AudioObjectID deviceId) {
|
|
UInt32 dataSourceId;
|
|
UInt32 size = sizeof(dataSourceId);
|
|
OSStatus result = AudioObjectGetPropertyData(deviceId, &kAudioMasterDataSourcePropertyAddress, 0, NULL, &size, &dataSourceId);
|
|
if (result != noErr)
|
|
return kAudioDeviceUnknown;
|
|
|
|
return dataSourceId;
|
|
}
|
|
|
|
QString translateDeviceSource(AudioObjectID deviceId) {
|
|
UInt32 sourceId = getDeviceSourceId(deviceId);
|
|
|
|
if (sourceId == kAudioDeviceUnknown) {
|
|
qWarning(KDECONNECT_PLUGIN_SYSTEMVOLUME) << "Unknown data source id of device" << deviceId;
|
|
return QStringLiteral("");
|
|
}
|
|
|
|
CFStringRef sourceName = nullptr;
|
|
AudioValueTranslation translation;
|
|
translation.mInputData = &sourceId;
|
|
translation.mInputDataSize = sizeof(sourceId);
|
|
translation.mOutputData = &sourceName;
|
|
translation.mOutputDataSize = sizeof(sourceName);
|
|
|
|
UInt32 translationSize = sizeof(AudioValueTranslation);
|
|
AudioObjectPropertyAddress propertyAddress = {
|
|
kAudioDevicePropertyDataSourceNameForIDCFString,
|
|
kAudioDevicePropertyScopeOutput,
|
|
kAudioObjectPropertyElementMaster};
|
|
|
|
OSStatus result = AudioObjectGetPropertyData(deviceId, &propertyAddress, 0, NULL, &translationSize, &translation);
|
|
if (result != noErr) {
|
|
qWarning(KDECONNECT_PLUGIN_SYSTEMVOLUME) << "Cannot get description of device" << deviceId;
|
|
return QStringLiteral("");
|
|
}
|
|
|
|
QString ret = QString::fromCFString(sourceName);
|
|
CFRelease(sourceName);
|
|
|
|
return ret;
|
|
}
|
|
|
|
std::vector<AudioObjectID> GetAllOutputAudioDeviceIDs() {
|
|
std::vector<AudioObjectID> outputDeviceIds;
|
|
|
|
UInt32 size = 0;
|
|
OSStatus result;
|
|
|
|
result = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &kAudioHardwarePropertyAddress, 0, NULL, &size);
|
|
|
|
if (result != noErr) {
|
|
qCDebug(KDECONNECT_PLUGIN_SYSTEMVOLUME)
|
|
<< "Failed to read size of property " << kAudioHardwarePropertyDevices
|
|
<< " for device/object " << kAudioObjectSystemObject;
|
|
return {};
|
|
}
|
|
|
|
if (size == 0)
|
|
return {};
|
|
|
|
size_t deviceCount = size / sizeof(AudioObjectID);
|
|
std::vector<AudioObjectID> deviceIds(deviceCount);
|
|
result = AudioObjectGetPropertyData(kAudioObjectSystemObject, &kAudioHardwarePropertyAddress, 0, NULL, &size, deviceIds.data());
|
|
if (result != noErr) {
|
|
qCDebug(KDECONNECT_PLUGIN_SYSTEMVOLUME)
|
|
<< "Failed to read object IDs from property " << kAudioHardwarePropertyDevices
|
|
<< " for device/object " << kAudioObjectSystemObject;
|
|
return {};
|
|
}
|
|
|
|
for (AudioDeviceID deviceId : deviceIds) {
|
|
UInt32 streamCount = 0;
|
|
result = AudioObjectGetPropertyDataSize(deviceId, &kAudioStreamPropertyAddress, 0, NULL, &streamCount);
|
|
|
|
if (result == noErr && streamCount > 0) {
|
|
outputDeviceIds.push_back(deviceId);
|
|
qCDebug(KDECONNECT_PLUGIN_SYSTEMVOLUME) << "Device" << deviceId << "added";
|
|
} else {
|
|
qCDebug(KDECONNECT_PLUGIN_SYSTEMVOLUME) << "Device" << deviceId << "dropped";
|
|
}
|
|
}
|
|
|
|
return outputDeviceIds;
|
|
}
|
|
|
|
SystemvolumePlugin::SystemvolumePlugin(QObject* parent, const QVariantList& args)
|
|
: KdeConnectPlugin(parent, args), m_sinksMap()
|
|
{}
|
|
|
|
bool SystemvolumePlugin::receivePacket(const NetworkPacket& np)
|
|
{
|
|
if (np.has(QStringLiteral("requestSinks"))) {
|
|
sendSinkList();
|
|
} else {
|
|
QString name = np.get<QString>(QStringLiteral("name"));
|
|
|
|
if (m_sinksMap.contains(name)) {
|
|
if (np.has(QStringLiteral("volume"))) {
|
|
m_sinksMap[name]->setVolume(np.get<int>(QStringLiteral("volume")) / 100.0);
|
|
}
|
|
if (np.has(QStringLiteral("muted"))) {
|
|
m_sinksMap[name]->setMuted(np.get<bool>(QStringLiteral("muted")));
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void SystemvolumePlugin::sendSinkList()
|
|
{
|
|
QJsonDocument document;
|
|
QJsonArray array;
|
|
|
|
if (!m_sinksMap.empty()) {
|
|
for (MacOSCoreAudioDevice *sink : m_sinksMap) {
|
|
delete sink;
|
|
}
|
|
m_sinksMap.clear();
|
|
}
|
|
|
|
std::vector<AudioObjectID> deviceIds = GetAllOutputAudioDeviceIDs();
|
|
|
|
for (AudioDeviceID deviceId : deviceIds) {
|
|
MacOSCoreAudioDevice *audioDevice = new MacOSCoreAudioDevice(deviceId);
|
|
|
|
audioDevice->m_description = translateDeviceSource(deviceId);
|
|
|
|
m_sinksMap.insert(QStringLiteral("default-") + QString::number(deviceId), audioDevice);
|
|
|
|
// Add volume change listener
|
|
AudioObjectAddPropertyListener(deviceId, &kAudioMasterVolumePropertyAddress, &onVolumeChanged, (void *)this);
|
|
|
|
AudioObjectAddPropertyListener(deviceId, &kAudioLeftVolumePropertyAddress, &onVolumeChanged, (void *)this);
|
|
AudioObjectAddPropertyListener(deviceId, &kAudioRightVolumePropertyAddress, &onVolumeChanged, (void *)this);
|
|
|
|
// Add muted change listener
|
|
AudioObjectAddPropertyListener(deviceId, &kAudioMasterMutedPropertyAddress, &onMutedChanged, (void *)this);
|
|
|
|
// Add data source change listerner
|
|
AudioObjectAddPropertyListener(deviceId, &kAudioMasterDataSourcePropertyAddress, &onOutputSourceChanged, (void *)this);
|
|
|
|
QJsonObject sinkObject {
|
|
{QStringLiteral("name"), QStringLiteral("default-") + QString::number(deviceId)},
|
|
{QStringLiteral("muted"), audioDevice->isMuted()},
|
|
{QStringLiteral("description"), audioDevice->m_description},
|
|
{QStringLiteral("volume"), audioDevice->volume() * 100},
|
|
{QStringLiteral("maxVolume"), 100}
|
|
};
|
|
|
|
array.append(sinkObject);
|
|
}
|
|
|
|
document.setArray(array);
|
|
|
|
NetworkPacket np(PACKET_TYPE_SYSTEMVOLUME);
|
|
np.set<QJsonDocument>(QStringLiteral("sinkList"), document);
|
|
sendPacket(np);
|
|
}
|
|
|
|
void SystemvolumePlugin::connected()
|
|
{
|
|
sendSinkList();
|
|
}
|
|
|
|
void SystemvolumePlugin::updateDeviceMuted(AudioDeviceID deviceId)
|
|
{
|
|
for (MacOSCoreAudioDevice *sink : m_sinksMap) {
|
|
if (sink->m_deviceId == deviceId) {
|
|
NetworkPacket np(PACKET_TYPE_SYSTEMVOLUME);
|
|
np.set<bool>(QStringLiteral("muted"), (bool)(sink->isMuted()));
|
|
np.set<int>(QStringLiteral("volume"), (int)(sink->volume() * 100));
|
|
np.set<QString>(QStringLiteral("name"), QStringLiteral("default-") + QString::number(deviceId));
|
|
sendPacket(np);
|
|
return;
|
|
}
|
|
}
|
|
qCDebug(KDECONNECT_PLUGIN_SYSTEMVOLUME) << "Device" << deviceId << "not found while update mute state";
|
|
}
|
|
|
|
void SystemvolumePlugin::updateDeviceVolume(AudioDeviceID deviceId)
|
|
{
|
|
for (MacOSCoreAudioDevice *sink : m_sinksMap) {
|
|
if (sink->m_deviceId == deviceId) {
|
|
NetworkPacket np(PACKET_TYPE_SYSTEMVOLUME);
|
|
np.set<int>(QStringLiteral("volume"), (int)(sink->volume() * 100));
|
|
np.set<QString>(QStringLiteral("name"), QStringLiteral("default-") + QString::number(deviceId));
|
|
sendPacket(np);
|
|
return;
|
|
}
|
|
}
|
|
qCDebug(KDECONNECT_PLUGIN_SYSTEMVOLUME) << "Device" << deviceId << "not found while update volume";
|
|
}
|
|
|
|
MacOSCoreAudioDevice::MacOSCoreAudioDevice(AudioDeviceID deviceId)
|
|
: m_deviceId(deviceId)
|
|
{
|
|
updateType();
|
|
}
|
|
|
|
MacOSCoreAudioDevice::~MacOSCoreAudioDevice()
|
|
{
|
|
// Volume listener
|
|
AudioObjectRemovePropertyListener(m_deviceId, &kAudioMasterVolumePropertyAddress,
|
|
&onVolumeChanged, (void *)this);
|
|
AudioObjectRemovePropertyListener(m_deviceId, &kAudioLeftVolumePropertyAddress,
|
|
&onVolumeChanged, (void *)this);
|
|
AudioObjectRemovePropertyListener(m_deviceId, &kAudioRightVolumePropertyAddress,
|
|
&onVolumeChanged, (void *)this);
|
|
|
|
// Muted listener
|
|
AudioObjectRemovePropertyListener(m_deviceId, &kAudioMasterMutedPropertyAddress,
|
|
&onMutedChanged, (void *)this);
|
|
|
|
// Data source listener
|
|
AudioObjectRemovePropertyListener(m_deviceId, &kAudioMasterDataSourcePropertyAddress,
|
|
&onOutputSourceChanged, (void *)this);
|
|
}
|
|
|
|
void MacOSCoreAudioDevice::setVolume(float volume)
|
|
{
|
|
OSStatus result;
|
|
|
|
if (m_deviceId == kAudioObjectUnknown) {
|
|
qWarning(KDECONNECT_PLUGIN_SYSTEMVOLUME) << "Unable to set volume of Unknown Device";
|
|
return;
|
|
}
|
|
|
|
if (m_isStereo) {
|
|
result = AudioObjectSetPropertyData(m_deviceId, &kAudioLeftVolumePropertyAddress, 0, NULL, sizeof(volume), &volume);
|
|
result = AudioObjectSetPropertyData(m_deviceId, &kAudioRightVolumePropertyAddress, 0, NULL, sizeof(volume), &volume);
|
|
} else {
|
|
result = AudioObjectSetPropertyData(m_deviceId, &kAudioMasterVolumePropertyAddress, 0, NULL, sizeof(volume), &volume);
|
|
}
|
|
|
|
if (result != noErr) {
|
|
qWarning(KDECONNECT_PLUGIN_SYSTEMVOLUME) << "Unable to set volume of Device" << m_deviceId << "to" << volume;
|
|
}
|
|
}
|
|
|
|
void MacOSCoreAudioDevice::setMuted(bool muted)
|
|
{
|
|
if (m_deviceId == kAudioObjectUnknown) {
|
|
qWarning(KDECONNECT_PLUGIN_SYSTEMVOLUME) << "Unable to mute an Unknown Device";
|
|
return;
|
|
}
|
|
|
|
UInt32 mutedValue = muted ? 1 : 0;
|
|
|
|
OSStatus result = AudioObjectSetPropertyData(m_deviceId, &kAudioMasterMutedPropertyAddress, 0, NULL, sizeof(mutedValue), &mutedValue);
|
|
|
|
if (result != noErr) {
|
|
qWarning(KDECONNECT_PLUGIN_SYSTEMVOLUME) << "Unable to set muted state of Device" << m_deviceId << "to" << muted;
|
|
}
|
|
}
|
|
|
|
float MacOSCoreAudioDevice::volume()
|
|
{
|
|
OSStatus result;
|
|
|
|
if (m_deviceId == kAudioObjectUnknown) {
|
|
qWarning(KDECONNECT_PLUGIN_SYSTEMVOLUME) << "Unable to get volume of Unknown Device";
|
|
return 0.0;
|
|
}
|
|
|
|
float volume = 0.0;
|
|
UInt32 volumeDataSize = sizeof(volume);
|
|
|
|
if (m_isStereo) {
|
|
// Try to get steoreo device volume
|
|
result = AudioObjectGetPropertyData(m_deviceId, &kAudioLeftVolumePropertyAddress, 0, NULL, &volumeDataSize, &volume);
|
|
} else {
|
|
// Try to get master volume
|
|
result = AudioObjectGetPropertyData(m_deviceId, &kAudioMasterVolumePropertyAddress, 0, NULL, &volumeDataSize, &volume);
|
|
}
|
|
|
|
if (result != noErr) {
|
|
qWarning(KDECONNECT_PLUGIN_SYSTEMVOLUME) << "Unable to get volume of Device" << m_deviceId;
|
|
return 0.0;
|
|
}
|
|
|
|
return volume;
|
|
}
|
|
|
|
bool MacOSCoreAudioDevice::isMuted()
|
|
{
|
|
if (m_deviceId == kAudioObjectUnknown) {
|
|
qWarning(KDECONNECT_PLUGIN_SYSTEMVOLUME) << "Unable to get muted state of an Unknown Device";
|
|
return false;
|
|
}
|
|
|
|
UInt32 muted = 0;
|
|
UInt32 muteddataSize = sizeof(muted);
|
|
|
|
AudioObjectGetPropertyData(m_deviceId, &kAudioMasterMutedPropertyAddress, 0, NULL, &muteddataSize, &muted);
|
|
|
|
return muted == 1;
|
|
}
|
|
|
|
void MacOSCoreAudioDevice::updateType()
|
|
{
|
|
// Try to get volume from left channel to check if it's a stereo device
|
|
float volume = 0.0;
|
|
UInt32 volumeDataSize = sizeof(volume);
|
|
OSStatus result = AudioObjectGetPropertyData(m_deviceId, &kAudioLeftVolumePropertyAddress, 0, NULL, &volumeDataSize, &volume);
|
|
if (result == noErr) {
|
|
m_isStereo = true;
|
|
} else {
|
|
m_isStereo = false;
|
|
}
|
|
}
|
|
|
|
|
|
#include "systemvolumeplugin-macos.moc"
|
|
|