kdeconnect-kde/plugins/systemvolume/systemvolumeplugin-macos.cpp
Alexander Lohnau 02d97aabf4 Add explicit moc includes to cpp files
The rationale is explained in https://planet.kde.org/friedrich-kossebau-2023-06-28-include-also-moc-files-of-headers/

In case of KDEConnect, it impressively speeds up compilation. Before it
took 390 seconds on a clean build and with this change it took 330 seconds.
This is due to the mocs_compilation having to include the header files
and thus all their headers. Due to the lots of small plugins we have,
this means that the same headers must be compiled plenty of times.
When we include the moc files directly in the C++ file, they are already
available.
2023-07-28 16:07:34 +02:00

467 lines
17 KiB
C++

/**
* SPDX-FileCopyrightText: 2019 Weixuan XIAO <veyx.shaw@gmail.com>
*
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
#include "systemvolumeplugin-macos.h"
#include <KPluginFactory>
#include "plugin_systemvolume_debug.h"
#include <QDebug>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
K_PLUGIN_CLASS_WITH_JSON(SystemvolumePlugin, "kdeconnect_systemvolume.json")
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 setDefault(bool enabled);
bool isDefault();
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};
static const AudioObjectPropertyAddress kAudioDefaultOutputDevicePropertyAddress = {kAudioHardwarePropertyDefaultOutputDevice,
kAudioObjectPropertyScopeGlobal,
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 onDefaultChanged(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;
}
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;
}
AudioObjectID getDefaultOutputDeviceId()
{
AudioObjectID dataSourceId;
UInt32 size = sizeof(dataSourceId);
OSStatus result = AudioObjectGetPropertyData(kAudioObjectSystemObject, &kAudioDefaultOutputDevicePropertyAddress, 0, NULL, &size, &dataSourceId);
if (result != noErr)
return kAudioDeviceUnknown;
return dataSourceId;
}
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()
{
}
SystemvolumePlugin::~SystemvolumePlugin()
{
AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &kAudioDefaultOutputDevicePropertyAddress, &onDefaultChanged, (void *)this);
}
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")));
}
if (np.has(QStringLiteral("enabled"))) {
m_sinksMap[name]->setDefault(np.get<bool>(QStringLiteral("enabled")));
}
}
}
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},
{QStringLiteral("enabled"), audioDevice->isDefault()}};
array.append(sinkObject);
}
document.setArray(array);
NetworkPacket np(PACKET_TYPE_SYSTEMVOLUME);
np.set<QJsonDocument>(QStringLiteral("sinkList"), document);
sendPacket(np);
}
void SystemvolumePlugin::connected()
{
AudioObjectAddPropertyListener(kAudioObjectSystemObject, &kAudioDefaultOutputDevicePropertyAddress, &onDefaultChanged, (void *)this);
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;
}
}
void MacOSCoreAudioDevice::setDefault(bool enabled)
{
if (!enabled)
return;
if (m_deviceId == kAudioObjectUnknown) {
qWarning(KDECONNECT_PLUGIN_SYSTEMVOLUME) << "Unable to set an Unknown Device as default output";
return;
}
OSStatus result = AudioObjectSetPropertyData(kAudioObjectSystemObject, &kAudioDefaultOutputDevicePropertyAddress, 0, NULL, sizeof(m_deviceId), &m_deviceId);
if (result != noErr) {
qWarning(KDECONNECT_PLUGIN_SYSTEMVOLUME) << "Unable to set default state of Device" << m_deviceId;
}
}
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;
}
bool MacOSCoreAudioDevice::isDefault()
{
AudioObjectID defaultDeviceId = getDefaultOutputDeviceId();
return m_deviceId == defaultDeviceId;
}
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 "moc_systemvolumeplugin-macos.cpp"
#include "systemvolumeplugin-macos.moc"