[OpenDocString] kdeconnect-kde (cpp)
systemvolumeplugin-macos.cpp
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;
}
This invokes the system volume plugin when the volume of the object has changed. It takes the object and property addresses of the object and updates the device volume of the plugin. It returns the status of the call.
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;
}
This invokes the system volume plugin to update the device muted status.
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;
}
This sends a list of sinks to the specified addresses.
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;
}
This sends a list of sinks to the specified objects.
AudioObjectID getDefaultOutputDeviceId()
{
    AudioObjectID dataSourceId;
    UInt32 size = sizeof(dataSourceId);
    OSStatus result = AudioObjectGetPropertyData(kAudioObjectSystemObject, &kAudioDefaultOutputDevicePropertyAddress, 0, NULL, &size, &dataSourceId);
    if (result != noErr)
        return kAudioDeviceUnknown;

    return dataSourceId;
}
This retrieves the default output device id from the audio object system.
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;
}
Returns the data source id of the given device id.
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;
}
This retrieves the data source id of the given device id, and translates it to its internal representation. It returns a QString object.
std::vector GetAllOutputAudioDeviceIDs()
{
    std::vector 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 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;
}
This retrieves the list of output audio device ids from the kAudioHardwarePropertyAddress, in the order of their size. If the property size is 0, the function retrieves the list of device ids from the kAudioHardwarePropertyAddress, and retrieves the list of device ids from the property data. If the property data is not available, the function retrieves the list of device ids from the kAudioHardwarePropertyAddresses, and retrieves the list of device ids from the property data. If there are no errors, the function returns an empty list.
SystemvolumePlugin::SystemvolumePlugin(QObject *parent, const QVariantList &args)
    : KdeConnectPlugin(parent, args)
    , m_sinksMap()
{
}
Constructs a plugin object and assigns its data to its internal sink map.
SystemvolumePlugin::~SystemvolumePlugin()
{
    AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &kAudioDefaultOutputDevicePropertyAddress, &onDefaultChanged, (void *)this);
}
This removes the audio property listener on the default output device property address.
bool SystemvolumePlugin::receivePacket(const NetworkPacket &np)
{
    if (np.has(QStringLiteral("requestSinks"))) {
        sendSinkList();
    } else {
        QString name = np.get(QStringLiteral("name"));

        if (m_sinksMap.contains(name)) {
            if (np.has(QStringLiteral("volume"))) {
                m_sinksMap[name]->setVolume(np.get(QStringLiteral("volume")) / 100.0);
            }
            if (np.has(QStringLiteral("muted"))) {
                m_sinksMap[name]->setMuted(np.get(QStringLiteral("muted")));
            }
            if (np.has(QStringLiteral("enabled"))) {
                m_sinksMap[name]->setDefault(np.get(QStringLiteral("enabled")));
            }
        }
    }

    return true;
}
This sends a network packet to the appropriate sink list.
void SystemvolumePlugin::sendSinkList()
{
    QJsonDocument document;
    QJsonArray array;

    if (!m_sinksMap.empty()) {
        for (MacOSCoreAudioDevice *sink : m_sinksMap) {
            delete sink;
        }
        m_sinksMap.clear();
    }

    std::vector 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(QStringLiteral("sinkList"), document);
    sendPacket(np);
}
This sends a list of sinks to all devices.
void SystemvolumePlugin::connected()
{
    AudioObjectAddPropertyListener(kAudioObjectSystemObject, &kAudioDefaultOutputDevicePropertyAddress, &onDefaultChanged, (void *)this);
    sendSinkList();
}
This adds a property listener to the default output device property address and sends the sink list to the AudioObjectSystemObject.
void SystemvolumePlugin::updateDeviceMuted(AudioDeviceID deviceId)
{
    for (MacOSCoreAudioDevice *sink : m_sinksMap) {
        if (sink->m_deviceId == deviceId) {
            NetworkPacket np(PACKET_TYPE_SYSTEMVOLUME);
            np.set(QStringLiteral("muted"), (bool)(sink->isMuted()));
            np.set(QStringLiteral("volume"), (int)(sink->volume() * 100));
            np.set(QStringLiteral("name"), QStringLiteral("default-") + QString::number(deviceId));
            sendPacket(np);
            return;
        }
    }
    qCDebug(KDECONNECT_PLUGIN_SYSTEMVOLUME) << "Device" << deviceId << "not found while update mute state";
}
This updates the muted state of the device by first searching for a device of the given deviceId. Then it creates a NetworkPacket object and sends it to the corresponding socket.
void SystemvolumePlugin::updateDeviceVolume(AudioDeviceID deviceId)
{
    for (MacOSCoreAudioDevice *sink : m_sinksMap) {
        if (sink->m_deviceId == deviceId) {
            NetworkPacket np(PACKET_TYPE_SYSTEMVOLUME);
            np.set(QStringLiteral("volume"), (int)(sink->volume() * 100));
            np.set(QStringLiteral("name"), QStringLiteral("default-") + QString::number(deviceId));
            sendPacket(np);
            return;
        }
    }
    qCDebug(KDECONNECT_PLUGIN_SYSTEMVOLUME) << "Device" << deviceId << "not found while update volume";
}
This sends a network packet to set the volume value of the given device id, and sets the default volume in the network package to 100% of the volume.
MacOSCoreAudioDevice::MacOSCoreAudioDevice(AudioDeviceID deviceId)
    : m_deviceId(deviceId)
{
    updateType();
}
Sets the device id of the macoscore audio device object.
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);
}
This removes the listeners of the audio properties of the given device.
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;
    }
}
Sets the volume value by setting the left and right volume properties of the audio device to the given value. If the device is not a stereo device, the function logs a warning.
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;
    }
}
Sets the muted state of the device.
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;
    }
}
This enables or disables the default output device of the MacOSCoreAudioDevice object. It logs a warning if the device is not enabled.
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;
}
This retrieves the volume value from the audio object. It first checks that the device is unknown, if it is a steoreo device, and if it is the master device, it gets the volume from the left or master volume property.
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;
}
This returns true if the device is muted. It gets the muted state from the audio object property of the internal device object. It first checks that the device id is not an Unknown Device, then checks that the property data is of the correct size. Then it checks that the property data is of the correct size.
bool MacOSCoreAudioDevice::isDefault()
{
    AudioObjectID defaultDeviceId = getDefaultOutputDeviceId();
    return m_deviceId == defaultDeviceId;
}
This implements checking if the device id is the default device id.
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;
    }
}
This retrieves the volume from the left channel of the MacOSCoreAudioDevice object, and checks if it is a stereo device.