File indexing completed on 2025-04-27 04:36:17
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"