File indexing completed on 2024-04-21 04:56:55

0001 /**
0002  * SPDX-FileCopyrightText: 2019 Weixuan XIAO <veyx.shaw@gmail.com>
0003  *
0004  * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0005  */
0006 
0007 #include "systemvolumeplugin-macos.h"
0008 
0009 #include <KPluginFactory>
0010 
0011 #include "plugin_systemvolume_debug.h"
0012 #include <QDebug>
0013 #include <QJsonArray>
0014 #include <QJsonDocument>
0015 #include <QJsonObject>
0016 
0017 K_PLUGIN_CLASS_WITH_JSON(SystemvolumePlugin, "kdeconnect_systemvolume.json")
0018 
0019 class MacOSCoreAudioDevice
0020 {
0021 private:
0022     AudioDeviceID m_deviceId;
0023     QString m_description;
0024     bool m_isStereo;
0025 
0026     friend class SystemvolumePlugin;
0027 
0028 public:
0029     MacOSCoreAudioDevice(AudioDeviceID);
0030     ~MacOSCoreAudioDevice();
0031 
0032     void setVolume(float volume);
0033     float volume();
0034     void setMuted(bool muted);
0035     bool isMuted();
0036     void setDefault(bool enabled);
0037     bool isDefault();
0038 
0039     void updateType();
0040 };
0041 
0042 static const AudioObjectPropertyAddress kAudioHardwarePropertyAddress = {kAudioHardwarePropertyDevices,
0043                                                                          kAudioObjectPropertyScopeGlobal,
0044                                                                          kAudioObjectPropertyElementMaster};
0045 
0046 static const AudioObjectPropertyAddress kAudioStreamPropertyAddress = {kAudioDevicePropertyStreams,
0047                                                                        kAudioDevicePropertyScopeOutput,
0048                                                                        kAudioObjectPropertyElementMaster};
0049 
0050 static const AudioObjectPropertyAddress kAudioMasterVolumePropertyAddress = {kAudioDevicePropertyVolumeScalar,
0051                                                                              kAudioDevicePropertyScopeOutput,
0052                                                                              kAudioObjectPropertyElementMaster};
0053 
0054 static const AudioObjectPropertyAddress kAudioLeftVolumePropertyAddress = {kAudioDevicePropertyVolumeScalar, kAudioDevicePropertyScopeOutput, 1};
0055 
0056 static const AudioObjectPropertyAddress kAudioRightVolumePropertyAddress = {kAudioDevicePropertyVolumeScalar, kAudioDevicePropertyScopeOutput, 2};
0057 
0058 static const AudioObjectPropertyAddress kAudioMasterMutedPropertyAddress = {kAudioDevicePropertyMute,
0059                                                                             kAudioDevicePropertyScopeOutput,
0060                                                                             kAudioObjectPropertyElementMaster};
0061 
0062 static const AudioObjectPropertyAddress kAudioMasterDataSourcePropertyAddress = {kAudioDevicePropertyDataSource,
0063                                                                                  kAudioDevicePropertyScopeOutput,
0064                                                                                  kAudioObjectPropertyElementMaster};
0065 
0066 static const AudioObjectPropertyAddress kAudioDefaultOutputDevicePropertyAddress = {kAudioHardwarePropertyDefaultOutputDevice,
0067                                                                                     kAudioObjectPropertyScopeGlobal,
0068                                                                                     kAudioObjectPropertyElementMaster};
0069 
0070 OSStatus onVolumeChanged(AudioObjectID object, UInt32 numAddresses, const AudioObjectPropertyAddress addresses[], void *context)
0071 {
0072     Q_UNUSED(object);
0073     Q_UNUSED(addresses);
0074     Q_UNUSED(numAddresses);
0075 
0076     SystemvolumePlugin *plugin = (SystemvolumePlugin *)context;
0077     plugin->updateDeviceVolume(object);
0078     return noErr;
0079 }
0080 
0081 OSStatus onMutedChanged(AudioObjectID object, UInt32 numAddresses, const AudioObjectPropertyAddress addresses[], void *context)
0082 {
0083     Q_UNUSED(object);
0084     Q_UNUSED(addresses);
0085     Q_UNUSED(numAddresses);
0086 
0087     SystemvolumePlugin *plugin = (SystemvolumePlugin *)context;
0088     plugin->updateDeviceMuted(object);
0089     return noErr;
0090 }
0091 
0092 OSStatus onDefaultChanged(AudioObjectID object, UInt32 numAddresses, const AudioObjectPropertyAddress addresses[], void *context)
0093 {
0094     Q_UNUSED(object);
0095     Q_UNUSED(addresses);
0096     Q_UNUSED(numAddresses);
0097 
0098     SystemvolumePlugin *plugin = (SystemvolumePlugin *)context;
0099     plugin->sendSinkList();
0100     return noErr;
0101 }
0102 
0103 OSStatus onOutputSourceChanged(AudioObjectID object, UInt32 numAddresses, const AudioObjectPropertyAddress addresses[], void *context)
0104 {
0105     Q_UNUSED(object);
0106     Q_UNUSED(addresses);
0107     Q_UNUSED(numAddresses);
0108 
0109     SystemvolumePlugin *plugin = (SystemvolumePlugin *)context;
0110     plugin->sendSinkList();
0111     return noErr;
0112 }
0113 
0114 AudioObjectID getDefaultOutputDeviceId()
0115 {
0116     AudioObjectID dataSourceId;
0117     UInt32 size = sizeof(dataSourceId);
0118     OSStatus result = AudioObjectGetPropertyData(kAudioObjectSystemObject, &kAudioDefaultOutputDevicePropertyAddress, 0, NULL, &size, &dataSourceId);
0119     if (result != noErr)
0120         return kAudioDeviceUnknown;
0121 
0122     return dataSourceId;
0123 }
0124 
0125 UInt32 getDeviceSourceId(AudioObjectID deviceId)
0126 {
0127     UInt32 dataSourceId;
0128     UInt32 size = sizeof(dataSourceId);
0129     OSStatus result = AudioObjectGetPropertyData(deviceId, &kAudioMasterDataSourcePropertyAddress, 0, NULL, &size, &dataSourceId);
0130     if (result != noErr)
0131         return kAudioDeviceUnknown;
0132 
0133     return dataSourceId;
0134 }
0135 
0136 QString translateDeviceSource(AudioObjectID deviceId)
0137 {
0138     UInt32 sourceId = getDeviceSourceId(deviceId);
0139 
0140     if (sourceId == kAudioDeviceUnknown) {
0141         qWarning(KDECONNECT_PLUGIN_SYSTEMVOLUME) << "Unknown data source id of device" << deviceId;
0142         return QStringLiteral("");
0143     }
0144 
0145     CFStringRef sourceName = nullptr;
0146     AudioValueTranslation translation;
0147     translation.mInputData = &sourceId;
0148     translation.mInputDataSize = sizeof(sourceId);
0149     translation.mOutputData = &sourceName;
0150     translation.mOutputDataSize = sizeof(sourceName);
0151 
0152     UInt32 translationSize = sizeof(AudioValueTranslation);
0153     AudioObjectPropertyAddress propertyAddress = {kAudioDevicePropertyDataSourceNameForIDCFString,
0154                                                   kAudioDevicePropertyScopeOutput,
0155                                                   kAudioObjectPropertyElementMaster};
0156 
0157     OSStatus result = AudioObjectGetPropertyData(deviceId, &propertyAddress, 0, NULL, &translationSize, &translation);
0158     if (result != noErr) {
0159         qWarning(KDECONNECT_PLUGIN_SYSTEMVOLUME) << "Cannot get description of device" << deviceId;
0160         return QStringLiteral("");
0161     }
0162 
0163     QString ret = QString::fromCFString(sourceName);
0164     CFRelease(sourceName);
0165 
0166     return ret;
0167 }
0168 
0169 std::vector<AudioObjectID> GetAllOutputAudioDeviceIDs()
0170 {
0171     std::vector<AudioObjectID> outputDeviceIds;
0172 
0173     UInt32 size = 0;
0174     OSStatus result;
0175 
0176     result = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &kAudioHardwarePropertyAddress, 0, NULL, &size);
0177 
0178     if (result != noErr) {
0179         qCDebug(KDECONNECT_PLUGIN_SYSTEMVOLUME) << "Failed to read size of property " << kAudioHardwarePropertyDevices << " for device/object "
0180                                                 << kAudioObjectSystemObject;
0181         return {};
0182     }
0183 
0184     if (size == 0)
0185         return {};
0186 
0187     size_t deviceCount = size / sizeof(AudioObjectID);
0188     std::vector<AudioObjectID> deviceIds(deviceCount);
0189     result = AudioObjectGetPropertyData(kAudioObjectSystemObject, &kAudioHardwarePropertyAddress, 0, NULL, &size, deviceIds.data());
0190     if (result != noErr) {
0191         qCDebug(KDECONNECT_PLUGIN_SYSTEMVOLUME) << "Failed to read object IDs from property " << kAudioHardwarePropertyDevices << " for device/object "
0192                                                 << kAudioObjectSystemObject;
0193         return {};
0194     }
0195 
0196     for (AudioDeviceID deviceId : deviceIds) {
0197         UInt32 streamCount = 0;
0198         result = AudioObjectGetPropertyDataSize(deviceId, &kAudioStreamPropertyAddress, 0, NULL, &streamCount);
0199 
0200         if (result == noErr && streamCount > 0) {
0201             outputDeviceIds.push_back(deviceId);
0202             qCDebug(KDECONNECT_PLUGIN_SYSTEMVOLUME) << "Device" << deviceId << "added";
0203         } else {
0204             qCDebug(KDECONNECT_PLUGIN_SYSTEMVOLUME) << "Device" << deviceId << "dropped";
0205         }
0206     }
0207 
0208     return outputDeviceIds;
0209 }
0210 
0211 SystemvolumePlugin::SystemvolumePlugin(QObject *parent, const QVariantList &args)
0212     : KdeConnectPlugin(parent, args)
0213     , m_sinksMap()
0214 {
0215 }
0216 
0217 SystemvolumePlugin::~SystemvolumePlugin()
0218 {
0219     AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &kAudioDefaultOutputDevicePropertyAddress, &onDefaultChanged, (void *)this);
0220 }
0221 
0222 void SystemvolumePlugin::receivePacket(const NetworkPacket &np)
0223 {
0224     if (np.has(QStringLiteral("requestSinks"))) {
0225         sendSinkList();
0226     } else {
0227         QString name = np.get<QString>(QStringLiteral("name"));
0228 
0229         if (m_sinksMap.contains(name)) {
0230             if (np.has(QStringLiteral("volume"))) {
0231                 m_sinksMap[name]->setVolume(np.get<int>(QStringLiteral("volume")) / 100.0);
0232             }
0233             if (np.has(QStringLiteral("muted"))) {
0234                 m_sinksMap[name]->setMuted(np.get<bool>(QStringLiteral("muted")));
0235             }
0236             if (np.has(QStringLiteral("enabled"))) {
0237                 m_sinksMap[name]->setDefault(np.get<bool>(QStringLiteral("enabled")));
0238             }
0239         }
0240     }
0241 }
0242 
0243 void SystemvolumePlugin::sendSinkList()
0244 {
0245     QJsonDocument document;
0246     QJsonArray array;
0247 
0248     if (!m_sinksMap.empty()) {
0249         for (MacOSCoreAudioDevice *sink : m_sinksMap) {
0250             delete sink;
0251         }
0252         m_sinksMap.clear();
0253     }
0254 
0255     std::vector<AudioObjectID> deviceIds = GetAllOutputAudioDeviceIDs();
0256 
0257     for (AudioDeviceID deviceId : deviceIds) {
0258         MacOSCoreAudioDevice *audioDevice = new MacOSCoreAudioDevice(deviceId);
0259 
0260         audioDevice->m_description = translateDeviceSource(deviceId);
0261 
0262         m_sinksMap.insert(QStringLiteral("default-") + QString::number(deviceId), audioDevice);
0263 
0264         // Add volume change listener
0265         AudioObjectAddPropertyListener(deviceId, &kAudioMasterVolumePropertyAddress, &onVolumeChanged, (void *)this);
0266 
0267         AudioObjectAddPropertyListener(deviceId, &kAudioLeftVolumePropertyAddress, &onVolumeChanged, (void *)this);
0268         AudioObjectAddPropertyListener(deviceId, &kAudioRightVolumePropertyAddress, &onVolumeChanged, (void *)this);
0269 
0270         // Add muted change listener
0271         AudioObjectAddPropertyListener(deviceId, &kAudioMasterMutedPropertyAddress, &onMutedChanged, (void *)this);
0272 
0273         // Add data source change listerner
0274         AudioObjectAddPropertyListener(deviceId, &kAudioMasterDataSourcePropertyAddress, &onOutputSourceChanged, (void *)this);
0275 
0276         QString name = QStringLiteral("default-") + QString::number(deviceId);
0277         QJsonObject sinkObject{{QStringLiteral("name"), name},
0278                                {QStringLiteral("muted"), audioDevice->isMuted()},
0279                                {QStringLiteral("description"), audioDevice->m_description},
0280                                {QStringLiteral("volume"), audioDevice->volume() * 100},
0281                                {QStringLiteral("maxVolume"), 100},
0282                                {QStringLiteral("enabled"), audioDevice->isDefault()}};
0283 
0284         array.append(sinkObject);
0285     }
0286 
0287     document.setArray(array);
0288 
0289     NetworkPacket np(PACKET_TYPE_SYSTEMVOLUME);
0290     np.set<QJsonDocument>(QStringLiteral("sinkList"), document);
0291     sendPacket(np);
0292 }
0293 
0294 void SystemvolumePlugin::connected()
0295 {
0296     AudioObjectAddPropertyListener(kAudioObjectSystemObject, &kAudioDefaultOutputDevicePropertyAddress, &onDefaultChanged, (void *)this);
0297     sendSinkList();
0298 }
0299 
0300 void SystemvolumePlugin::updateDeviceMuted(AudioDeviceID deviceId)
0301 {
0302     for (MacOSCoreAudioDevice *sink : m_sinksMap) {
0303         if (sink->m_deviceId == deviceId) {
0304             NetworkPacket np(PACKET_TYPE_SYSTEMVOLUME);
0305             np.set<bool>(QStringLiteral("muted"), (bool)(sink->isMuted()));
0306             np.set<int>(QStringLiteral("volume"), (int)(sink->volume() * 100));
0307             np.set<QString>(QStringLiteral("name"), QStringLiteral("default-") + QString::number(deviceId));
0308             sendPacket(np);
0309             return;
0310         }
0311     }
0312     qCDebug(KDECONNECT_PLUGIN_SYSTEMVOLUME) << "Device" << deviceId << "not found while update mute state";
0313 }
0314 
0315 void SystemvolumePlugin::updateDeviceVolume(AudioDeviceID deviceId)
0316 {
0317     for (MacOSCoreAudioDevice *sink : m_sinksMap) {
0318         if (sink->m_deviceId == deviceId) {
0319             NetworkPacket np(PACKET_TYPE_SYSTEMVOLUME);
0320             np.set<int>(QStringLiteral("volume"), (int)(sink->volume() * 100));
0321             np.set<QString>(QStringLiteral("name"), QStringLiteral("default-") + QString::number(deviceId));
0322             sendPacket(np);
0323             return;
0324         }
0325     }
0326     qCDebug(KDECONNECT_PLUGIN_SYSTEMVOLUME) << "Device" << deviceId << "not found while update volume";
0327 }
0328 
0329 MacOSCoreAudioDevice::MacOSCoreAudioDevice(AudioDeviceID deviceId)
0330     : m_deviceId(deviceId)
0331 {
0332     updateType();
0333 }
0334 
0335 MacOSCoreAudioDevice::~MacOSCoreAudioDevice()
0336 {
0337     // Volume listener
0338     AudioObjectRemovePropertyListener(m_deviceId, &kAudioMasterVolumePropertyAddress, &onVolumeChanged, (void *)this);
0339     AudioObjectRemovePropertyListener(m_deviceId, &kAudioLeftVolumePropertyAddress, &onVolumeChanged, (void *)this);
0340     AudioObjectRemovePropertyListener(m_deviceId, &kAudioRightVolumePropertyAddress, &onVolumeChanged, (void *)this);
0341 
0342     // Muted listener
0343     AudioObjectRemovePropertyListener(m_deviceId, &kAudioMasterMutedPropertyAddress, &onMutedChanged, (void *)this);
0344 
0345     // Data source listener
0346     AudioObjectRemovePropertyListener(m_deviceId, &kAudioMasterDataSourcePropertyAddress, &onOutputSourceChanged, (void *)this);
0347 }
0348 
0349 void MacOSCoreAudioDevice::setVolume(float volume)
0350 {
0351     OSStatus result;
0352 
0353     if (m_deviceId == kAudioObjectUnknown) {
0354         qWarning(KDECONNECT_PLUGIN_SYSTEMVOLUME) << "Unable to set volume of Unknown Device";
0355         return;
0356     }
0357 
0358     if (m_isStereo) {
0359         result = AudioObjectSetPropertyData(m_deviceId, &kAudioLeftVolumePropertyAddress, 0, NULL, sizeof(volume), &volume);
0360         result = AudioObjectSetPropertyData(m_deviceId, &kAudioRightVolumePropertyAddress, 0, NULL, sizeof(volume), &volume);
0361     } else {
0362         result = AudioObjectSetPropertyData(m_deviceId, &kAudioMasterVolumePropertyAddress, 0, NULL, sizeof(volume), &volume);
0363     }
0364 
0365     if (result != noErr) {
0366         qWarning(KDECONNECT_PLUGIN_SYSTEMVOLUME) << "Unable to set volume of Device" << m_deviceId << "to" << volume;
0367     }
0368 }
0369 
0370 void MacOSCoreAudioDevice::setMuted(bool muted)
0371 {
0372     if (m_deviceId == kAudioObjectUnknown) {
0373         qWarning(KDECONNECT_PLUGIN_SYSTEMVOLUME) << "Unable to mute an Unknown Device";
0374         return;
0375     }
0376 
0377     UInt32 mutedValue = muted ? 1 : 0;
0378 
0379     OSStatus result = AudioObjectSetPropertyData(m_deviceId, &kAudioMasterMutedPropertyAddress, 0, NULL, sizeof(mutedValue), &mutedValue);
0380 
0381     if (result != noErr) {
0382         qWarning(KDECONNECT_PLUGIN_SYSTEMVOLUME) << "Unable to set muted state of Device" << m_deviceId << "to" << muted;
0383     }
0384 }
0385 
0386 void MacOSCoreAudioDevice::setDefault(bool enabled)
0387 {
0388     if (!enabled)
0389         return;
0390 
0391     if (m_deviceId == kAudioObjectUnknown) {
0392         qWarning(KDECONNECT_PLUGIN_SYSTEMVOLUME) << "Unable to set an Unknown Device as default output";
0393         return;
0394     }
0395 
0396     OSStatus result = AudioObjectSetPropertyData(kAudioObjectSystemObject, &kAudioDefaultOutputDevicePropertyAddress, 0, NULL, sizeof(m_deviceId), &m_deviceId);
0397 
0398     if (result != noErr) {
0399         qWarning(KDECONNECT_PLUGIN_SYSTEMVOLUME) << "Unable to set default state of Device" << m_deviceId;
0400     }
0401 }
0402 
0403 float MacOSCoreAudioDevice::volume()
0404 {
0405     OSStatus result;
0406 
0407     if (m_deviceId == kAudioObjectUnknown) {
0408         qWarning(KDECONNECT_PLUGIN_SYSTEMVOLUME) << "Unable to get volume of Unknown Device";
0409         return 0.0;
0410     }
0411 
0412     float volume = 0.0;
0413     UInt32 volumeDataSize = sizeof(volume);
0414 
0415     if (m_isStereo) {
0416         // Try to get steoreo device volume
0417         result = AudioObjectGetPropertyData(m_deviceId, &kAudioLeftVolumePropertyAddress, 0, NULL, &volumeDataSize, &volume);
0418     } else {
0419         // Try to get master volume
0420         result = AudioObjectGetPropertyData(m_deviceId, &kAudioMasterVolumePropertyAddress, 0, NULL, &volumeDataSize, &volume);
0421     }
0422 
0423     if (result != noErr) {
0424         qWarning(KDECONNECT_PLUGIN_SYSTEMVOLUME) << "Unable to get volume of Device" << m_deviceId;
0425         return 0.0;
0426     }
0427 
0428     return volume;
0429 }
0430 
0431 bool MacOSCoreAudioDevice::isMuted()
0432 {
0433     if (m_deviceId == kAudioObjectUnknown) {
0434         qWarning(KDECONNECT_PLUGIN_SYSTEMVOLUME) << "Unable to get muted state of an Unknown Device";
0435         return false;
0436     }
0437 
0438     UInt32 muted = 0;
0439     UInt32 muteddataSize = sizeof(muted);
0440 
0441     AudioObjectGetPropertyData(m_deviceId, &kAudioMasterMutedPropertyAddress, 0, NULL, &muteddataSize, &muted);
0442 
0443     return muted == 1;
0444 }
0445 
0446 bool MacOSCoreAudioDevice::isDefault()
0447 {
0448     AudioObjectID defaultDeviceId = getDefaultOutputDeviceId();
0449     return m_deviceId == defaultDeviceId;
0450 }
0451 
0452 void MacOSCoreAudioDevice::updateType()
0453 {
0454     // Try to get volume from left channel to check if it's a stereo device
0455     float volume = 0.0;
0456     UInt32 volumeDataSize = sizeof(volume);
0457     OSStatus result = AudioObjectGetPropertyData(m_deviceId, &kAudioLeftVolumePropertyAddress, 0, NULL, &volumeDataSize, &volume);
0458     if (result == noErr) {
0459         m_isStereo = true;
0460     } else {
0461         m_isStereo = false;
0462     }
0463 }
0464 
0465 #include "moc_systemvolumeplugin-macos.cpp"
0466 #include "systemvolumeplugin-macos.moc"